简而言之:使用 StringIO 无法避免 2 个副本。
一些假设:
- 您正在使用 cStringIO,否则优化这么多会很愚蠢。
- 您追求的是速度而不是内存效率。如果不是,请参阅 Jakob Bowyer 的解决方案,或者如果您的文件是二进制文件,请使用
file.read(SOME_BYTE_COUNT) 的变体。
- 您已经在 cmets 中说明了这一点,但为了完整起见:您希望实际编辑内容,而不仅仅是查看它。
长答案:由于 python 字符串是不可变的,而 StringIO 缓冲区不是,因此迟早必须进行复制;否则你会改变一个不可变的对象!对于您希望实现的功能,StringIO 对象需要有一个专门的方法,该方法可以直接从作为参数给出的文件对象中读取。没有这种方法。
在StringIO之外,有一些解决方案可以避免额外的拷贝。在我的脑海中,这会将文件直接读取到可修改的字节数组中,没有额外的副本:
import numpy as np
a = np.fromfile("filename.ext", dtype="uint8")
使用起来可能很麻烦,具体取决于您的用途,因为它是一个从 0 到 255 的值数组,而不是字符数组。但它在功能上等同于 StringIO 对象,使用 np.fromstring、np.tostring、np.tofile 和切片符号应该可以让您到达您想要的位置。您可能还需要np.insert、np.delete 和np.append。
我相信还有其他模块可以做类似的事情。
时间:
这一切真的有多少重要?走着瞧。我制作了一个 100MB 的文件,largefile.bin。然后我使用这两种方法读取文件并更改第一个字节。
$ python -m timeit -s "将 numpy 导入为 np" "a = np.fromfile('largefile.bin', 'uint8'); a[0] = 1"
10 个循环,3 个循环中的最佳:每个循环 132 毫秒
$ python -m timeit -s "从 cStringIO 导入 StringIO" "a = StringIO(); a.write(open('largefile.bin').read()); a.seek(0); a.write(' 1')"
10 个循环,最好的 3 个:每个循环 203 毫秒
所以在我的例子中,使用 StringIO 比使用 numpy 慢 50%。
最后,为了比较,直接编辑文件:
$ python -m timeit "a = open('largefile.bin', 'r+b'); a.seek(0); a.write('1')"
10000 个循环,最好的 3 个:每个循环 29.5 微秒
因此,它的速度快了近 4500 倍。当然,这在很大程度上取决于您要对文件做什么。改变第一个字节几乎没有代表性。但是使用这种方法,您确实可以在其他两个方面取得领先,并且由于大多数操作系统具有良好的磁盘缓冲,因此速度也可能非常好。
(如果您不允许编辑文件并希望避免制作工作副本的成本,有几种可能的方法可以提高速度。如果您可以选择文件系统,Btrfs 有copy-on-write 文件复制操作 -- 使复制文件的行为几乎是即时的。使用任何文件系统的 LVM 快照可以达到相同的效果。)