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

时间:2011-11-23 10:30:09

标签: python stream

在Python中我有一个文件流,我想将其中的一部分复制到StringIO。我希望尽可能快地使用最少的副本。

但如果我这样做:

data = file.read(SIZE)
stream = StringIO(data)

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

4 个答案:

答案 0 :(得分: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")

使用它可能很麻烦,具体取决于您的用途,因为它是一个0到255之间的值数组,而不是一个字符数组。但它在功能上等同于StringIO对象,并且使用np.fromstringnp.tostringnp.tofile和切片表示法可以让您到达目的地。您可能还需要np.insertnp.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倍。当然,它非常依赖于你要对文件做什么。改变第一个字节几乎没有代表性。但是使用这种方法,你确实在其他两个方面有了先机,而且由于大多数操作系统都有良好的磁盘缓冲,因此速度也可能非常好。

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

答案 1 :(得分: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'

快速浏览the source表示cStringIO也没有在构造上制作副本,但它会在调用cStringIO.getvalue()时制作副本,所以我不能重复以上示范。

答案 2 :(得分:2)

也许您正在寻找的是buffer/memoryview

>>> data = file.read(SIZE)
>>> buf = buffer(data, 0, len(data))

这样您就可以访问原始数据的一部分而无需复制它。但是,您必须只对以字节为导向的格式访问该数据感兴趣,因为这是缓冲区协议提供的内容。

您可以在此相关question

中找到更多相关信息

编辑:在我通过reddit找到的blog post中,提供了有关同一问题的更多信息:

>>> f = open.(filename, 'rb')
>>> data = bytearray(os.path.getsize(filename))
>>> f.readinto(data)

根据作者的说法,由于bytearray是可变的,因此不会创建额外的副本并且可以修改数据。

答案 3 :(得分:0)

stream = StringIO()
for line in file:
    stream.write(line + "\n")