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,如您所见;这需要大约 1/4 秒来索引和 50 毫秒来读取和打印出三个随机 y 行(毫无疑问,这可以通过更仔细的优化和更好的机器大大加速)。内存中的索引(以及磁盘上的索引)每行被索引的文本文件占用 4 个字节,并且性能应该以完美的线性方式扩展,所以如果你有大约 1 亿行,4.4 GB,我预计大约 4-5建立索引需要几分钟,提取并打印出任意三行需要一分钟(并且为索引占用的 400 MB RAM 甚至不会给小型机器带来不便——即使我的小型慢速笔记本电脑毕竟也有 2GB 空间;-)。
您还可以看到(为了速度和方便)我将文件视为二进制文件(并假设 utf8 编码——当然也适用于像 ASCII 这样的任何子集,例如 KJ 文本文件是 ASCII)并且不要打扰如果文件具有作为行终止符的内容,则将 \r\n 折叠为单个字符(如果需要,在读取每一行之后这样做非常简单)。