Python seek
переходит к смещению байт в файле, а не к смещению line , просто потому, что так работают современные операционные системы и их файловые системы - ОС / FS просто не записывает и не запоминает «смещения строк», и у Python (или любого другого языка) просто нет возможности волшебным образом угадать их. Любая операция, предполагающая «перейти к строке», неизбежно должна будет «пройтись по файлу» (под обложками), чтобы установить связь между номерами строк и смещениями байтов.
Если вы согласны с этим и хотите, чтобы это было скрыто от глаз, то решением является стандартный библиотечный модуль linecache - но производительность не будет лучше, чем у кода, который вы могли бы пиши сам.
Если вам нужно прочитать один и тот же большой файл несколько раз, большая оптимизация будет состоять в том, чтобы один раз запустить для этого большого файла сценарий, который создает и сохраняет на диск смещение числа строк в байтах переписка (технически «индексный» вспомогательный файл); затем все ваши последовательные прогоны (до изменения большого файла) могут очень быстро использовать индексный файл для очень высокой производительности навигации по большому файлу. Это ваш вариант использования ...?
Редактировать : поскольку, очевидно, это применимо - вот общая идея (за исключением тщательного тестирования, проверки ошибок или оптимизации ;-). Чтобы создать индекс, используйте makeindex.py
следующим образом:
import array
import sys
BLOCKSIZE = 1024 * 1024
def reader(f):
blockstart = 0
while True:
block = f.read(BLOCKSIZE)
if not block: break
inblock = 0
while True:
nextnl = block.find(b'\n', inblock)
if nextnl < 0:
blockstart += len(block)
break
yield nextnl + blockstart
inblock = nextnl + 1
def doindex(fn):
with open(fn, 'rb') as f:
# result format: x[0] is tot # of lines,
# x[N] is byte offset of END of line N (1+)
result = array.array('L', [0])
result.extend(reader(f))
result[0] = len(result) - 1
return result
def main():
for fn in sys.argv[1:]:
index = doindex(fn)
with open(fn + '.indx', 'wb') as p:
print('File', fn, 'has', index[0], 'lines')
index.tofile(p)
main()
и затем использовать его, например, следующее useindex.py
:
import array
import sys
def readline(n, f, findex):
f.seek(findex[n] + 1)
bytes = f.read(findex[n+1] - findex[n])
return bytes.decode('utf8')
def main():
fn = sys.argv[1]
with open(fn + '.indx', 'rb') as f:
findex = array.array('l')
findex.fromfile(f, 1)
findex.fromfile(f, findex[0])
findex[0] = -1
with open(fn, 'rb') as f:
for n in sys.argv[2:]:
print(n, repr(readline(int(n), f, findex)))
main()
Вот пример (на моем медленном ноутбуке):
$ time py3 makeindex.py kjv10.txt
File kjv10.txt has 100117 lines
real 0m0.235s
user 0m0.184s
sys 0m0.035s
$ time py3 useindex.py kjv10.txt 12345 98765 33448
12345 '\r\n'
98765 '2:6 But this thou hast, that thou hatest the deeds of the\r\n'
33448 'the priest appointed officers over the house of the LORD.\r\n'
real 0m0.049s
user 0m0.028s
sys 0m0.020s
$
Пример файла представляет собой простой текстовый файл Библии короля Иакова:
$ wc kjv10.txt
100117 823156 4445260 kjv10.txt
100K строк, 4,4 МБ, как видите; для индексации требуется около четверти секунды и 50 миллисекунд для считывания и распечатки трех случайных y-строк (без сомнения, это может быть значительно ускорено при более тщательной оптимизации и улучшенной машине). Индекс в памяти (и на диске тоже) занимает 4 байта на строку индексируемого текстового файла, а производительность должна масштабироваться совершенно линейно, поэтому, если у вас около 100 миллионов строк, 4,4 ГБ, я бы ожидал около 4-5 минуты для создания индекса, минуты для извлечения и распечатки трех произвольных строк (и 400 МБ ОЗУ, выделенного для индекса, не должны доставлять неудобств даже маленькой машине - даже мой крошечный медленный ноутбук имеет 2 ГБ в конце концов; -).
Вы также можете видеть, что (для скорости и удобства) я рассматриваю файл как двоичный файл (и предполагаю, что кодировка utf8 - работает с любым подмножеством, например ASCII, например, этот текстовый файл KJ является ASCII) и не беспокоюсь сворачивание \r\n
в один символ, если в файле есть терминатор строки (это довольно просто сделать после чтения каждой строки, если хотите).