Перемещение в произвольную позицию в файле в Python - PullRequest
9 голосов
/ 07 июня 2010

Допустим, мне обычно приходится работать с файлами с неизвестным, но большим количеством строк.Каждая строка содержит набор целых чисел (пробел, запятая, точка с запятой или некоторый нечисловой символ, являющийся разделителем) в закрытом интервале [0, R], где R может быть сколь угодно большим.Количество целых чисел в каждой строке может быть переменным.Часто я получаю одинаковое количество целых чисел в каждой строке, но иногда у меня появляются строки с неравными наборами чисел.

Предположим, я хочу перейти к N-й строке в файле и получить K-е число в этой строке (и предположим, что входы N и K действительны - то есть я не беспокоюсь о плохих входах).Как мне сделать это эффективно в Python 3.1.2 для Windows?

Я не хочу обходить файл построчно.

Я пытался использовать mmap, но пока выискивал здесьв SO я узнал, что это, вероятно, не лучшее решение для 32-битной сборки из-за ограничения в 4 ГБИ по правде говоря, я не мог понять, как просто отодвинуть N строк от моей текущей позиции.Если я могу хотя бы просто «перейти» на N-ю строку, тогда я могу использовать .split () и получить K-е целое число таким образом.

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

Есть идеи?Надеюсь, этого достаточно.

Спасибо!

Ответы [ 3 ]

16 голосов
/ 07 июня 2010

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 в один символ, если в файле есть терминатор строки (это довольно просто сделать после чтения каждой строки, если хотите).

4 голосов
/ 07 июня 2010

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

0 голосов
/ 07 июня 2010

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

Это может быть излишним.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...