在Python中,我有一个文件流,我想将它的一部分复制到StringIO
中。我希望这是最快的,以最小的副本。
但是如果我这样做了:
data = file.read(SIZE)
stream = StringIO(data)
我想做了2份,不是吗?一个拷贝到文件中的数据,另一个拷贝到StringIO
中的内部缓冲区。我能避免复印一份吗?我不需要临时的data
,所以我认为一个副本应该足够了
简而言之:你不能避免使用StringIO的2个副本。
一些假设:
- 你正在使用cStringIO,否则优化这么多会很愚蠢。 你追求的是速度而不是内存效率。如果没有,请参阅Jakob Bowyer的解决方案,或者如果您的文件是二进制文件,则使用
- 您已经在评论中说明了这一点,但为了完整性:您想要实际编辑内容,而不仅仅是查看它。
file.read(SOME_BYTE_COUNT)
的变体。长答:由于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
。然后我使用这两种方法读入文件并更改第一个字节。
所以在我的例子中,使用StringIO比使用numpy慢50%。
最后,为了比较,直接编辑文件:
<>之前$ python -m timeit "a = open('largefile.bin', 'r+b');a.seek (0);a.write(1)"10000个循环,最好是3个,每个循环使用29.5次之前所以,它快了近4500倍。当然,这很大程度上取决于您要对文件做什么。改变第一个字节几乎没有代表性。但是使用这种方法,您确实比其他两种方法领先一步,并且由于大多数操作系统都有良好的磁盘缓冲,因此速度也可能非常好。
(如果不允许编辑文件,因此想要避免制作工作副本的成本,那么有几种可能的方法可以提高速度。如果可以选择文件系统,Btrfs有一个写时复制的文件复制操作——使得获取文件副本的行为几乎是即时的。使用任何文件系统的LVM快照也可以达到相同的效果。
不,没有多余的副本。用于存储数据的缓冲区是相同的。data
和使用StringIO.getvalue()
访问的内部属性是相同数据的不同名称。
Python 2.7 (r27:82500, Jul 30 2010, 07:39:35)
[GCC 4.1.2 20080704 (Red Hat 4.1.2-48)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import StringIO
>>> data = open("/dev/zero").read(1024)
>>> hex(id(data))
'0xea516f0'
>>> stream = StringIO.StringIO(data)
>>> hex(id(stream.getvalue()))
'0xea516f0'
快速浏览源代码显示,cStringIO
也没有复制构造,但它确实复制调用cStringIO.getvalue()
,所以我不能重复上面的演示。
也许你正在寻找的是一个buffer/memoryview:
>>> data = file.read(SIZE)
>>> buf = buffer(data, 0, len(data))
这种方式可以访问原始数据的切片,而无需复制它。但是,您必须对仅以字节为导向的格式访问数据感兴趣,因为这是缓冲区协议提供的。
你可以在这个相关问题中找到更多的信息。
编辑:在这篇博客文章中,我通过reddit找到了一些关于同样问题的更多信息:
>>> f = open.(filename, 'rb')
>>> data = bytearray(os.path.getsize(filename))
>>> f.readinto(data)
根据作者没有额外的副本创建和数据可以修改,因为bytearray
是可变的。
stream = StringIO()
for line in file:
stream.write(line + "n")