实际上,您不需要将整个文件读入内存。在

csv.DictReader实际上并不需要文件,只需要一个字符串的iterable。*

你可以在平均线性时间内以相反的顺序读取一个文本文件,而不需要太多的开销。这不是小事,但也不是那么难:

def reverse_lines(*args, **kwargs):
with open(*args, **kwargs) as f:
buf = ''
f.seek(0, io.SEEK_END)
while f.tell():
try:
f.seek(-1024, io.SEEK_CUR)
except OSError:
bufsize = f.tell()
f.seek(0, io.SEEK_SET)
newbuf = f.read(bufsize)
f.seek(0, io.SEEK_SET)
else:
newbuf = f.read(1024)
f.seek(-1024, io.SEEK_CUR)
buf = newbuf + buf
lines = buf.split('\n')
buf = lines.pop(0)
yield from reversed(lines)
yield buf

这不是经过严格测试的,它去掉了新行(这对csv.DictReader很好,但一般来说不好),而且它没有针对异常但可能的边缘情况进行优化(例如,对于非常长的行,它将是二次的),并且它需要Python3.3,并且在关闭/释放迭代器之前文件不会消失(可能应该是这样)一个上下文管理器,这样你就可以处理这个问题了),但是如果你真的想要这个,我敢打赌你可以在ActiveState或PyPI上找到一个没有这些问题的方法。在

不管怎样,对于一个中等大小的文件,我怀疑在几乎任何实际的文件系统上,以正向顺序将整个文件读入内存,然后反向迭代列表,这实际上会更快。但是对于一个非常大的文件(尤其是一个你甚至无法放入内存的文件),这个解决方案显然要好得多。

在我的电脑上,通过一个快速测试(代码见http://pastebin.com/Nst6WFwV),基本的故障是:对于文件<<1000行,速度要慢得多。在

从1K-1M线路下降约10%。在

30米左右的交叉线。在

500米线路速度提高50%。在

1.5G线路速度快1300%。在

实际上,在2.5G行上速度无限快(列表反转版本将我的机器扔进交换地狱,我必须通过ssh来终止进程,并等待几分钟才能恢复……)。在

当然,细节将取决于你的电脑的许多事实。这可能不是巧合,500M72字符行的ASCII占用了我机器上接近一半的物理RAM。但是使用硬盘驱动器而不是SSD,您可能会看到更多的惩罚reverse_lines(因为随机查找比连续读取慢得多,而且磁盘通常更重要)。平台的malloc和VM行为,甚至是位置问题(几乎在读取一行代码后立即对其进行解析,而不是在它被换出和返回之后……)可能会产生影响。等等。在

总之,教训是,如果您不希望至少有100万行代码(或者在资源受限的机器上可能会少一点),那么就不要考虑这个问题;只要保持简单就行了。在

*正如Martijn Pieters在评论中指出的,如果您不使用显式的fieldnames,DictReader需要一个字符串iterable,其中第一行是头。但是,您可以通过使用csv.reader分别读取第一行并将其作为fieldnames传递,或者甚至可以通过itertools.chain-在除向后读取的最后一行之外的所有前向读取中的所有第一行来修复该问题。