python - 在 Python 中移动到文件中的任意位置

标签 python file python-3.x

假设我经常需要处理行数未知但数量很多的文件。每行在闭区间 [0, R] 中包含一组整数(空格、逗号、分号或一些非数字字符作为分隔符),其中 R 可以任意大。每行上的整数数量可以是可变的。很多时候我在每行中得到相同数量的整数,但偶尔我的行中有不相等的数字集。

假设我想转到文件中的第 N 行并检索该行的第 K 个数字(并假设输入 N 和 K 有效——也就是说,我不担心输入错误)。我如何在适用于 Windows 的 Python 3.1.2 中高效地执行此操作?

我不想逐行遍历文件。

我尝试使用 mmap,但在 SO 上闲逛时,我了解到由于 4GB 的限制,这可能不是 32 位构建的最佳解决方案。事实上,我真的不知道如何简单地将 N 行从我当前的位置移开。如果我至少可以“跳”到第 N 行,那么我可以使用 .split() 并以这种方式获取第 K 个整数。

这里的细微差别是我不只是需要从文件中抓取一行。我需要抓取几行:它们不一定彼此靠近,我获取它们的顺序很重要,而且顺序并不总是基于某些确定性函数。

有什么想法吗?我希望这是足够的信息。

谢谢!

最佳答案

Python 的 seek 定位到文件中的 byte 偏移量,而不是 line 偏移量,因为这是现代操作系统和他们的文件系统工作——OS/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 MB;这需要大约四分之一秒的索引时间和 50 毫秒的时间来读取和打印出三个随机 y 行(毫无疑问,通过更仔细的优化和更好的机器,这可以大大加速)。内存中的索引(以及磁盘上的索引)每行被索引的文本文件占用 4 个字节,性能应该以完美的线性方式扩展,所以如果你有大约 1 亿行,4.4 GB,我预计大约 4-5分钟来构建索引,一分钟来提取和打印任意三行(索引占用的 400 MB RAM 即使是小型机器也不应该给您带来不便——毕竟我的微型慢笔记本电脑也有 2GB;-)。

您还可以看到(为了速度和方便)我将文件视为二进制文件(并假定为 utf8 编码——当然也适用于任何子集,例如 ASCII,例如 KJ 文本文件是 ASCII)并且不要打扰将 \r\n 折叠成单个字符,如果这是文件作为行终止符的字符(如果需要,阅读每一行之后执行此操作非常简单)。

关于python - 在 Python 中移动到文件中的任意位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2985725/

相关文章:

python - SQL炼金术: "create schema if not exists"

python - 随机分割数据帧(取决于唯一值)

java - 为什么在 Linux 中使用 Files.copy() 复制文件时出现 NoSuchFileException。但在 Windows 中工作正常。

shell - 在 linux 中,文件不为空但大小为 0

python - 从 python 中的文本文件中读取带有正则表达式的文件片段

python - 比较对象并在列表中搜索

python - 名称错误 : name 'BillPayer' is not defined

python - 为什么我的 python 程序在使用调试器运行时不会抛出错误?

python - 导入错误 : cannot import name 'IntEnum'

python - 根据 2 个条件从数据框中删除行