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



在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.insertnp.deletenp.append

我相信还有其他模块会做类似的事情。

时间

:

这些到底有多重要?好吧,让我看看。我做了一个100MB的文件,largefile.bin。然后我使用这两种方法读入文件并更改第一个字节。

<>之前$ python -m timeit -s "import numpy as np" "a = np.fromfile('largefile.bin', 'uint8');A [0] = 1"10个循环,最好是3个,每循环132毫秒$ python -m timeit -s "from cStringIO import StringIO" "a = StringIO();a.write(开放(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有一个写时复制的文件复制操作——使得获取文件副本的行为几乎是即时的。使用任何文件系统的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")

相关内容

  • 没有找到相关文章

最新更新