Какой самый эффективный способ получить первую и последнюю строку текстового файла? - PullRequest
64 голосов
/ 27 июля 2010

У меня есть текстовый файл, который содержит отметку времени в каждой строке. Моя цель - найти диапазон времени. Все времена в порядке, поэтому первая строка будет самым ранним временем, а последняя строка будет самым последним временем. Мне нужна только самая первая и последняя строка. Какой самый эффективный способ получить эти строки в Python?

Примечание. Эти файлы имеют относительно большую длину, около 1-2 миллионов строк каждый, и я должен сделать это для нескольких сотен файлов.

Ответы [ 12 ]

67 голосов
/ 04 сентября 2013

Вы можете открыть файл для чтения и прочитать первую строку, используя встроенную строку readline(), затем перейти к концу файла и вернуться назад, пока не найдете строку, предшествующую EOL и прочитайте последнюю строку оттуда.

with open(file, "rb") as f:
    first = f.readline()        # Read the first line.
    f.seek(-2, os.SEEK_END)     # Jump to the second last byte.
    while f.read(1) != b"\n":   # Until EOL is found...
        f.seek(-2, os.SEEK_CUR) # ...jump back the read byte plus one more.
    last = f.readline()         # Read last line.

Переход ко второму последнему байту вместо последнего предотвращает возврат непосредственно из-за конечного EOL. Делая шаг назад, вы также захотите сделать шаг в два байта, поскольку чтение и проверка EOL сдвигают позицию вперед на один шаг.

При использовании seek формат fseek(offset, whence=0), где whence указывает на то, к чему относится смещение. Цитата docs.python.org :

  • SEEK_SET или 0 = поиск с начала потока (по умолчанию); смещение должно быть либо числом, возвращаемым TextIOBase.tell () или ноль. Любое другое значение смещения производит неопределенное поведение.
  • SEEK_CUR или 1 = «искать» текущую позицию; смещение должно быть равно нулю, что означает отсутствие операции (все остальные значения не поддерживается).
  • SEEK_END или 2 = поиск конца потока; смещение должно быть равно нулю (все остальные значения не поддерживаются).

Выполнение его через timeit 10k раз для файла с 6k строками общим объемом 200 КБ дало мне 1,62 с 6,92 с при сравнении с циклом for, предложенным ранее. При использовании файла размером 1,3 ГБ, все еще с 6 тыс. Строк, сто раз получилось 8,93 против 86,95.

with open(file, "rb") as f:
    first = f.readline()     # Read the first line.
    for last in f: pass      # Loop through the whole file reading it all.
55 голосов
/ 27 июля 2010

документы для модуля io

with open(fname, 'rb') as fh:
    first = next(fh).decode()

    fh.seek(-1024, 2)
    last = fh.readlines()[-1].decode()

Значение переменной здесь 1024: она представляет среднюю длину строки. Я выбираю 1024 только для примера. Если у вас есть оценка средней длины линии, вы можете просто использовать это значение раз 2.

Поскольку вы не имеете никакого представления о возможной верхней границе длины строки, очевидным решением будет цикл по файлу:

for line in fh:
    pass
last = line

Вам не нужно беспокоиться о бинарном флаге, который вы можете просто использовать open(fname).

ETA : поскольку у вас есть много файлов для работы, вы можете создать образец пары десятков файлов, используя random.sample, и запустить этот код на них, чтобы определить длину последней строки. С априори большим значением сдвига позиции (скажем, 1 МБ). Это поможет вам оценить значение для полного прогона.

23 голосов
/ 27 июля 2010

Вот модифицированная версия ответа SilentGhost, которая будет делать то, что вы хотите.

with open(fname, 'rb') as fh:
    first = next(fh)
    offs = -100
    while True:
        fh.seek(offs, 2)
        lines = fh.readlines()
        if len(lines)>1:
            last = lines[-1]
            break
        offs *= 2
    print first
    print last

Здесь нет необходимости в верхней границе длины строки.

9 голосов
/ 27 июля 2010

Можете ли вы использовать команды Unix?Я думаю, что использование head -1 и tail -n 1, вероятно, являются наиболее эффективными методами.В качестве альтернативы, вы можете использовать простой fid.readline() для получения первой строки и fid.readlines()[-1], но это может занять слишком много памяти.

6 голосов
/ 31 мая 2016

Это мое решение, совместимое также с Python3. Он также управляет пограничными случаями, но пропускает поддержку utf-16:

def tail(filepath):
    """
    @author Marco Sulla (marcosullaroma@gmail.com)
    @date May 31, 2016
    """

    try:
        filepath.is_file
        fp = str(filepath)
    except AttributeError:
        fp = filepath

    with open(fp, "rb") as f:
        size = os.stat(fp).st_size
        start_pos = 0 if size - 1 < 0 else size - 1

        if start_pos != 0:
            f.seek(start_pos)
            char = f.read(1)

            if char == b"\n":
                start_pos -= 1
                f.seek(start_pos)

            if start_pos == 0:
                f.seek(start_pos)
            else:
                char = ""

                for pos in range(start_pos, -1, -1):
                    f.seek(pos)

                    char = f.read(1)

                    if char == b"\n":
                        break

        return f.readline()

Это связано с ответом Trasp и комментарием другого участника .

4 голосов
/ 30 октября 2014
w=open(file.txt, 'r')
print ('first line is : ',w.readline())
for line in w:  
    x= line
print ('last line is : ',x)
w.close()

Цикл for проходит по строкам, а x получает последнюю строку в последней итерации.

4 голосов
/ 06 сентября 2013

Сначала откройте файл в режиме чтения. Затем используйте метод readlines () для чтения построчно. Все строки хранятся в списке. Теперь вы можете использовать фрагменты списка для получения первой и последней строк файла.

    a=open('file.txt','rb')
    lines = a.readlines()
    if lines:
        first_line = lines[:1]
        last_line = lines[-1]
2 голосов
/ 20 июня 2018

Никто не упоминал, используя обратное:

f=open(file,"r")
r=reversed(f.readlines())
last_line_of_file = r.next()
2 голосов
/ 05 января 2017

Вот расширение ответа @ Trasp, в котором есть дополнительная логика для обработки углового случая файла, который имеет только одну строку.Это может быть полезно для обработки этого случая, если вы неоднократно хотите прочитать последнюю строку файла, который постоянно обновляется.Без этого, если вы попытаетесь получить последнюю строку файла, который был только что создан и имеет только одну строку, будет IOError: [Errno 22] Invalid argument.

def tail(filepath):
    with open(filepath, "rb") as f:
        first = f.readline()      # Read the first line.
        f.seek(-2, 2)             # Jump to the second last byte.
        while f.read(1) != b"\n": # Until EOL is found...
            try:
                f.seek(-2, 1)     # ...jump back the read byte plus one more.
            except IOError:
                f.seek(-1, 1)
                if f.tell() == 0:
                    break
        last = f.readline()       # Read last line.
    return last
2 голосов
/ 31 января 2015
with open("myfile.txt") as f:
    lines = f.readlines()
    first_row = lines[0]
    print first_row
    last_row = lines[-1]
    print last_row
...