Как перейти к определенной строке в огромном текстовом файле?

107

Есть ли альтернативы приведенному ниже коду:

startFromLine = 141978 # or whatever line I need to jump to

urlsfile = open(filename, "rb", 0)

linesCounter = 1

for line in urlsfile:
    if linesCounter > startFromLine:
        DoSomethingWithThisLine(line)

    linesCounter += 1

Если я обрабатываю огромный текстовый файл (~15MB)со строками неизвестной, но разной длины, и мне нужно перейти к определенной строке, какой номер я знаю заранее? Я чувствую себя плохо, обрабатывая их один за другим, когда знаю, что могу проигнорировать хотя бы первую половину файла. Ищу более элегантное решение, если оно есть.

user63503
источник
Откуда вы знаете, что первая половина файла не состоит из "\ n", а вторая половина - это одна строка? Почему тебе это плохо?
Эндрю Далк,
7
Я думаю, что название вводит в заблуждение - tbh 15MB на самом деле не "огромный текстовый файл",
мягко

Ответы:

30

linecache :

linecacheМодуль позволяет получить любую строку из исходного файла Python, при попытке оптимизировать внутренне, используя кэш, общий случай , когда многие линии считываются из одного файла. Это используется tracebackмодулем для получения исходных строк для включения в отформатированную трассировку ...

Джон Эллинвуд
источник
165
Я только что проверил исходный код этого модуля: в память читается весь файл! Поэтому я определенно исключил бы этот ответ с целью быстрого доступа к данной строке в файле.
MiniQuark 06
MiniQuark, попробовал, действительно работает, причем очень быстро. Мне нужно посмотреть, что произойдет, если я буду работать с десятком файлов одновременно таким образом, выяснить, в какой момент моя система умирает.
user63503 06
5
Диспетчер виртуальной памяти вашей ОС очень помогает, поэтому чтение больших файлов в память может быть не медленным, если вы не генерируете много ошибок страниц :) Напротив, делая это «глупым способом» и выделяя много-много памяти может быть невероятно быстрым. Мне понравилась статья датского разработчика FreeBSD Пола-Хеннинга Кампа по этому поводу
Мортен Йенсен
13
попробуйте файл 100G, это отстой. я должен использовать f.tell (), f.seek (), f.readline ()
бея
115

Вы не можете продвигаться вперед, не прочитав файл хотя бы один раз, так как вы не знаете, где находятся разрывы строк. Вы можете сделать что-то вроде:

# Read in the file once and build a list of line offsets
line_offset = []
offset = 0
for line in file:
    line_offset.append(offset)
    offset += len(line)
file.seek(0)

# Now, to skip to line n (with the first line being line 0), just do
file.seek(line_offset[n])
Адам Розенфилд
источник
2
+1, но учтите, что это полезно, только если он собирается перейти на несколько случайных строк! но если он прыгает только на одну строчку, то это расточительно
hasen 06
3
+1: Кроме того, если файл не изменяется, индекс номера строки может быть обработан и повторно использован, дополнительно амортизируя начальную стоимость сканирования файла.
S.Lott
Хорошо, после того, как я прыгнул туда, как я буду обрабатывать затем построчно, начиная с этой позиции?
user63503 06
8
Одна вещь, на которую следует обратить внимание (особенно в Windows): будьте осторожны, открывайте файл в двоичном режиме или используйте offset = file.tell (). В текстовом режиме в Windows строка будет на байт короче, чем ее исходная длина на диске (\ r \ n заменяется на \ n)
Брайан
2
@photographer: используйте read () или readline (), они начинаются с текущей позиции, установленной функцией поиска.
S.Lott
22

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

Однако вы можете значительно ускорить это И уменьшить использование памяти, изменив последний параметр на «open» на значение, отличное от 0.

0 означает, что операция чтения файла не буферизуется, что очень медленно и занимает много места на диске. 1 означает, что файл буферизирован по строкам, что было бы улучшением. Все, что выше 1 (скажем, 8k ... то есть: 8096 или выше), читает фрагменты файла в память. Вы по-прежнему for line in open(etc):получаете к нему доступ , но python работает только понемногу, отбрасывая каждый буферизованный фрагмент после его обработки.

Джаррет Харди
источник
6
8K это 8192, возможно, лучше написать 8 << 10 на всякий случай. :)
раскрутись
Вы случайно не знаете, что размер буфера указывается в байтах? Какой подходящий формат? Могу я написать «8к»? Или это должно быть 8096?
user63503 06
1
ХАХАХА ... должно быть пятница ... Я явно не умею считать. Размер буфера действительно является целым числом, выражающим байты, поэтому напишите 8192 (не 8096 :-)), а не 8
Джаррет Харди,
С удовольствием - надеюсь, что это сработает. В современной системе вы, вероятно, можете немного увеличить размер буфера. 8k - это просто пережиток в моей памяти по какой-то причине, которую я не могу определить.
Джаррет Харди,
Я провел здесь небольшое тестирование и установил его в -1 (по умолчанию ОС, часто 8k, но часто трудно сказать), похоже, примерно так же быстро, как и получается. Тем не менее, отчасти это может быть связано с тем, что я тестирую на виртуальном сервере.
Оскар Смит
12

Я наверное избалован обильным тараном, но 15 М - это не так уж и много. Чтение в память с помощью readlines() - это то, что я обычно делаю с файлами такого размера. Доступ к строке после этого тривиален.

Тихий призрак
источник
Почему я немного не решался читать файл целиком - у меня могло быть несколько таких процессов, и если дюжина из них прочитала 12 файлов по 15 МБ каждый, это могло быть не очень хорошо. Но мне нужно протестировать его, чтобы узнать, сработает ли он. Спасибо.
user63503 06
4
Хрм, а что если это файл размером 1Гб?
Ной
@photographer: даже "несколько" процессов, читающих файлы размером 15 МБ, не должны иметь значения на типичной современной машине (в зависимости, конечно, от того, что именно вы с ними делаете).
Джейкоб Гэбриэлсон,
Джейкоб, да, я должен просто попробовать. Процесс (ы) работает / работают на виртуальной машине в течение нескольких недель, если vm не сбой. К сожалению, в прошлый раз он разбился через 6 дней. Мне нужно продолжить с того места, где он внезапно остановился. Еще нужно выяснить, как найти, где он остался.
user63503 06
@Noah: но это не так! Почему бы тебе не пойти дальше? Что делать, если файл 128ТБ? Многие ОС не смогли бы его поддерживать. Почему бы не решить проблемы по мере их появления?
SilentGhost 06
7

Я удивлен, что никто не упомянул islice

line = next(itertools.islice(Fhandle,index_of_interest,index_of_interest+1),None) # just the one line

или если вы хотите, чтобы весь остальной файл

rest_of_file = itertools.islice(Fhandle,index_of_interest)
for line in rest_of_file:
    print line

или если вы хотите, чтобы каждая вторая строка из файла

rest_of_file = itertools.islice(Fhandle,index_of_interest,None,2)
for odd_line in rest_of_file:
    print odd_line
Джоран Бизли
источник
5

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

from itertools import dropwhile

def iterate_from_line(f, start_from_line):
    return (l for i, l in dropwhile(lambda x: x[0] < start_from_line, enumerate(f)))

for line in iterate_from_line(open(filename, "r", 0), 141978):
    DoSomethingWithThisLine(line)

Примечание: при таком подходе индекс равен нулю.


источник
4

Если вы не хотите читать весь файл в памяти ... вам может потребоваться какой-нибудь формат, отличный от обычного текста.

конечно, все зависит от того, что вы пытаетесь сделать, и как часто вы будете перепрыгивать через файл.

Например, если вы собираетесь много раз переходить к строкам в одном и том же файле и знаете, что файл не изменяется во время работы с ним, вы можете сделать это:
сначала пройти через весь файл и записать " seek-location "некоторых номеров ключевых строк (например, когда-либо 1000 строк).
Затем, если вы хотите строку 12005, перейдите на позицию 12000 (которую вы записали), затем прочтите 5 строк, и вы узнаете себя в очереди 12005 и так далее

hasen
источник
3

Если вы заранее знаете позицию в файле (а не номер строки), вы можете использовать file.seek () для перехода к этой позиции.

Изменить : вы можете использовать функцию linecache.getline (имя файла, белье) , которая вернет содержимое строки белье, но только после чтения всего файла в память. Хорошо, если вы произвольно обращаетесь к строкам из файла (как сам python может захотеть распечатать трассировку), но не подходит для файла размером 15 МБ.

Ной
источник
Я бы определенно не использовал для этой цели linecache, потому что он считывает весь файл в памяти перед возвратом запрошенной строки.
MiniQuark 06
Да, это звучало слишком хорошо, чтобы быть правдой. Мне все еще хотелось бы, чтобы был модуль, который бы это делал эффективно, но я предпочитаю использовать вместо него метод file.seek ().
Ной
3

Что создает файл, который вы хотите обработать? Если это что-то под вашим контролем, вы можете сгенерировать индекс (какая строка в какой позиции) во время добавления файла. Индексный файл может иметь фиксированный размер строки (заполненные пробелами или 0 числа) и определенно будет меньше. И поэтому их можно быстро читать и обрабатывать.

  • Какая линия вам нужна?
  • Вычислить байтовое смещение соответствующего номера строки в индексном файле (возможно, потому что размер строки индексного файла постоянный).
  • Используйте поиск или что-то еще, чтобы напрямую перейти к строке из индексного файла.
  • Выполните синтаксический анализ, чтобы получить смещение в байтах для соответствующей строки фактического файла.
Kamathln
источник
3

У меня была такая же проблема (нужно получить из огромной строки файла).

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

Я узнал следующее решение: сначала я заполнил словарь с начальной позицией каждой строки (ключ - номер строки, а значение - суммарная длина предыдущих строк).

t = open(file,’r’)
dict_pos = {}

kolvo = 0
length = 0
for each in t:
    dict_pos[kolvo] = length
    length = length+len(each)
    kolvo = kolvo+1

в конечном итоге прицельная функция:

def give_line(line_number):
    t.seek(dict_pos.get(line_number))
    line = t.readline()
    return line

t.seek (номер_строки) - команда, выполняющая обрезку файла до начала строки. Итак, если вы в следующий раз зафиксируете строку чтения - вы получите целевую строку.

Используя такой подход, я сэкономил значительную часть времени.

user3810114
источник
3

Вы можете использовать mmap, чтобы найти смещение линий. MMap кажется самым быстрым способом обработки файла

пример:

with open('input_file', "r+b") as f:
    mapped = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
    i = 1
    for line in iter(mapped.readline, ""):
        if i == Line_I_want_to_jump:
            offsets = mapped.tell()
        i+=1

затем используйте f.seek (смещения), чтобы перейти к нужной строке

Джордж
источник
2

Содержат ли сами строки какую-либо информацию индекса? Если бы содержание каждой строки было чем-то вроде " <line index>:Data", то seek()подход можно было бы использовать для выполнения двоичного поиска по файлу, даже если количествоData переменных. Вы должны искать середину файла, читать строку, проверять, выше или ниже ее индекс, чем тот, который вам нужен, и т. Д.

В противном случае лучшее, что вы можете сделать, - это просто readlines(). Если вы не хотите читать все 15 МБ, вы можете использовать sizehintаргумент, чтобы, по крайней мере, заменить много readline()s меньшим количеством вызовов readlines().

DNS
источник
2

Если вы имеете дело с текстовым файлом, основанным на системе Linux , вы можете использовать команды linux.
Для меня это сработало!

import commands

def read_line(path, line=1):
    return commands.getoutput('head -%s %s | tail -1' % (line, path))

line_to_jump = 141978
read_line("path_to_large_text_file", line_to_jump)
Гонконг Ю
источник
конечно, он несовместим с Windows или другими оболочками Linux, которые не поддерживают голову / хвост.
Wizmann
Это быстрее, чем в Python?
Shamoon
Можно ли получить несколько строк?
Shamoon
1

Вот пример использования readlines (sizehint) для чтения фрагмента строк за раз. DNS указал на это решение. Я написал этот пример, потому что другие примеры здесь ориентированы на одну строку.

def getlineno(filename, lineno):
    if lineno < 1:
        raise TypeError("First line is line 1")
    f = open(filename)
    lines_read = 0
    while 1:
        lines = f.readlines(100000)
        if not lines:
            return None
        if lines_read + len(lines) >= lineno:
            return lines[lineno-lines_read-1]
        lines_read += len(lines)

print getlineno("nci_09425001_09450000.smi", 12000)
Эндрю Далке
источник
1

Ни один из ответов не является особенно удовлетворительным, поэтому вот небольшой фрагмент, который поможет.

class LineSeekableFile:
    def __init__(self, seekable):
        self.fin = seekable
        self.line_map = list() # Map from line index -> file position.
        self.line_map.append(0)
        while seekable.readline():
            self.line_map.append(seekable.tell())

    def __getitem__(self, index):
        # NOTE: This assumes that you're not reading the file sequentially.  
        # For that, just use 'for line in file'.
        self.fin.seek(self.line_map[index])
        return self.fin.readline()

Пример использования:

In: !cat /tmp/test.txt

Out:
Line zero.
Line one!

Line three.
End of file, line four.

In:
with open("/tmp/test.txt", 'rt') as fin:
    seeker = LineSeekableFile(fin)    
    print(seeker[1])
Out:
Line one!

Это включает в себя множество поисков файлов, но это полезно в тех случаях, когда вы не можете уместить весь файл в памяти. Он выполняет одно начальное чтение, чтобы получить расположение строк (так что он читает весь файл, но не сохраняет его в памяти), а затем каждый доступ выполняет поиск файла постфактум.

Я предлагаю приведенный выше фрагмент под лицензией MIT или Apache на усмотрение пользователя.

Джозеф Катрамбоун
источник
1
Это лучшее решение не только для вопроса, но и для многих других проблем, связанных с памятью при чтении больших файлов. Спасибо вам за это!
Лукас Азеведо
-2

Можно использовать эту функцию для возврата строки n:

def skipton(infile, n):
    with open(infile,'r') as fi:
        for i in range(n-1):
            fi.next()
        return fi.next()
ksed
источник
Эта логика не работает, если есть непрерывные пустые строки, fi.next () пропускает все пустые строки сразу, в противном случае это хорошо :)
Анвеш Яламарти
OP не упоминает, что в строках есть строки с нестандартными переносами строк. В этом случае вам придется анализировать каждую строку по крайней мере с одним оператором if для частичных разрывов строки.
ksed