我正试图通过一个大文本文件(~232GB)搜索一些关键字.我想利用缓冲来解决速度问题,并希望记录包含这些关键字的行的起始位置.
我在这里看到很多帖子讨论类似的问题.但是,那些具有缓冲(使用文件作为迭代器)的解决方案无法提供正确的文件位置,并且这些解决方案通常只使用正确的文件位置f.readline(),而不使用缓冲.
我看到都可以做的唯一的答案是在这里:
# 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])
Run Code Online (Sandbox Code Playgroud)
但是,我不确定该offset += len(line)操作是否会花费不必要的时间.有没有更直接的方法来做到这一点?
我已经完成了一些计时,但似乎.readline()比使用文件对象作为迭代器要慢得多python 2.7.3.我使用了以下代码
#!/usr/bin/python
from timeit import timeit
MAX_LINES = 10000000
# use file object as iterator
def read_iter():
with open('tweets.txt','r') as f:
lino = 0
for line in f:
lino+=1
if lino == MAX_LINES:
break
# use .readline()
def read_readline():
with open('tweets.txt', 'r') as f:
lino = 0
for line in iter(f.readline,''):
lino+=1
if lino == MAX_LINES:
break
# use offset+=len(line) to simulate f.tell() under binary mode
def read_iter_tell():
offset = 0
with open('tweets.txt','rb') as f:
lino = 0
for line in f:
lino+=1
offset+=len(line)
if lino == MAX_LINES:
break
# use f.tell() with .readline()
def read_readline_tell():
with open('tweets.txt', 'rb') as f:
lino = 0
for line in iter(f.readline,''):
lino+=1
offset = f.tell()
if lino == MAX_LINES:
break
print ("iter: %f"%timeit("read_iter()",number=1,setup="from __main__ import read_iter"))
print ("readline: %f"%timeit("read_readline()",number=1,setup="from __main__ import read_readline"))
print ("iter_tell: %f"%timeit("read_iter_tell()",number=1,setup="from __main__ import read_iter_tell"))
print ("readline_tell: %f"%timeit("read_readline_tell()",number=1,setup="from __main__ import read_readline_tell"))
Run Code Online (Sandbox Code Playgroud)
结果如下:
iter: 5.079951
readline: 37.333189
iter_tell: 5.775822
readline_tell: 38.629598
Run Code Online (Sandbox Code Playgroud)
使用有什么问题.readline()?
您找到的示例对于在文本模式下打开的文件不正确.它应该在Linux系统上运行正常,但在Windows上运行不正常.在Windows上,返回文本模式文件中以前位置的唯一方法是寻找以下之一:
0(文件开头).
文件结束.
以前归来的职位f.tell().
您无法以任何便携方式计算文本模式文件位置.
所以使用.readline()和/或.read(),和.tell().问题解决了 ;-)
关于缓冲:是否使用缓冲与访问文件的方式无关; 它与文件的打开方式完全相关.缓冲是一个实现细节.特别是,f.readline()肯定会在封面下进行缓冲(除非您在文件open()调用中明确禁用了缓冲),但这种方式对您来说是不可见的.使用文件对象作为迭代器时发现的问题与文件迭代器实现(文档称为"隐藏的预读缓冲区")添加的额外缓冲层有关file.next().
要回答您的其他问题,费用为:
offset += len(line)
Run Code Online (Sandbox Code Playgroud)
这是微不足道的 - 但是,如前所述,"解决方案"存在实际问题.
短期课程:不要过早地变得棘手.做最简单的事情(比如.readline()+ .tell()),并且只有在被证明是不合适时才开始担心.
实际上有几层缓冲正在进行中.在硬件中,磁盘驱动器内部有内存缓冲区.在此之上,您的操作系统也会维护内存缓冲区,并且当您以统一模式访问文件时,通常会尝试"智能",要求磁盘驱动器在您正在读取的方向上"预读"磁盘块,超出你已经要求的块.
CPython的I/O构建在平台C的I/O库之上.C库有自己的内存缓冲区.为了使Python f.tell()"正常工作",CPython必须以C指令的方式使用C库.
现在,关于"一条线"的任何内容都没有什么特别之处(好吧,不是在任何主要的操作系统上)."一条线"是一种软件概念,通常意味着"直到并包括下一个\n字节(Linux),\r字节(某些Mac风格)或\r\n字节对(Windows).硬件,操作系统和C缓冲区通常不会知道关于"线"的任何事情 - 它们只是使用字节流.
在幕后,Python的.readline()基本上是"读"的,直到它看到结束线平台的字节序列一次一个字节(\n,\r,或\r\n).我将"读取"放在引号中,因为通常不涉及磁盘访问 - 它通常只是各种级别的软件从内存缓冲区复制字节.当一个磁盘访问的参与,这是数千次慢.
通过这样做"一次一个字节",C级库保持正确的结果f.tell().但是代价是:对于获得的每个字节,可能存在多层函数调用.
Python的文件迭代器一次"读取" 大块的字节,进入自己的内存缓冲区."多少"并不重要;-)重要的是它要求C库一次复制多个字节,然后CPython通过自己的内存缓冲区搜索行结束序列.这大大减少了所需的函数调用次数.但是以不同的代价:C库对文件中我们所处位置的想法反映了读入文件迭代器的内存缓冲区的字节数,这与用户的Python程序检索的字节数没有任何关系.从那个缓冲区.
所以,确实,for line in file:通常是逐行浏览整个文本文件的最快方法.
有关系吗?确切知道的唯一方法是将其计入实际数据.使用200 GB以上的文件进行读取时,您将花费数千倍的时间进行物理磁盘读取,而不是用于搜索行尾字节序列的各种软件层.
如果事实证明它确实很重要,并且您的数据和操作系统是这样的,您可以在二进制模式下打开文件并仍然得到正确的结果,那么您找到的代码片段将提供两全其美(最快线迭代,和正确的字节位置以便以后使用.seek().