快速数据从文件移动到某些StringIO

zah*_*pov 8 python stream

在Python中我有一个文件流,我想将它的一部分复制到一个StringIO.我想要尽可能快,最小的副本.

但如果我这样做:

data = file.read(SIZE)
stream = StringIO(data)
Run Code Online (Sandbox Code Playgroud)

我认为已完成2份,不是吗?从文件中复制一份数据,将另一份复制StringIO到内部缓冲区中.我可以避免其中一个副本吗?我不需要临时的data,所以我认为一份副本应该足够了

Lau*_*low 8

简而言之:使用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")
Run Code Online (Sandbox Code Playgroud)

使用它可能很麻烦,具体取决于您的用途,因为它是一个0到255之间的值数组,而不是一个字符数组.而且它的功能相当于一个StringIO对象,并使用np.fromstring,np.tostring,np.tofile并在您想要切片符号应该得到你.你可能也需要np.insert,np.deletenp.append.

我确信还有其他模块会做类似的事情.

TIMEIT:

这一切真的有多重要?好的,我们等着瞧.我已经制作了一个100MB的文件largefile.bin.然后我使用两种方法读入文件并更改第一个字节.

$ python -m timeit -s "import numpy as np" "a = np.fromfile('largefile.bin', 'uint8'); a[0] = 1"
10 loops, best of 3: 132 msec per loop
$ python -m timeit -s "from cStringIO import StringIO" "a = StringIO(); a.write(open('largefile.bin').read()); a.seek(0); a.write('1')"
10 loops, best of 3: 203 msec per loop

所以在我的情况下,使用StringIO比使用numpy慢50%.

最后,为了比较,直接编辑文件:

$ python -m timeit "a = open('largefile.bin', 'r+b'); a.seek(0); a.write('1')"
10000 loops, best of 3: 29.5 usec per loop

所以,它快了近4500倍.当然,它非常依赖于你要对文件做什么.改变第一个字节几乎没有代表性.但是使用这种方法,你可以在其他两个方面有一个良好的开端,并且由于大多数操作系统具有良好的磁盘缓冲,因此速度也可能非常好.

(如果您不允许编辑文件,因此希望避免制作工作副本的成本,有几种可能的方法来提高速度.如果您可以选择文件系统,Btrfs有一个副本 -写文件复制操作 - 使文件的副本几乎是即时的.使用任何文件系统的LVM快照可以实现相同的效果.)


Mic*_*man 6

不,没有额外的副本.用于存储数据的缓冲区是相同的.两者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'
Run Code Online (Sandbox Code Playgroud)

快速浏览一下来源显示,cStringIO它既不会在构造上复制cStringIO.getvalue(),也会在调用时复制,所以我不能重复上面的演示.