从文件到字符串的快速数据移动

2024-04-19 10:33:01 发布

您现在位置:Python中文网/ 问答频道 /正文

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

但如果我这样做了:

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

我想两份已经完成了,不是吗?一份拷贝到文件中的数据,另一份拷贝到内部缓冲区中的StringIO。我能避开其中一份吗?我不需要临时的data,所以我认为一份就足够了


Tags: 文件数据readdatastreamsize副本file
3条回答

不,没有多余的复印件。用于存储数据的缓冲区是相同的。对于同一数据,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()时它确实会生成副本,因此我不能重复上面的演示。

也许你要找的是一个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是可变的,因此不会创建额外的副本和修改数据。

简而言之:使用StringIO不能避免两个副本。

一些假设:

  • 你用的是cStringIO,否则优化这么多就太傻了。
  • 你追求的是速度而不是记忆效率。如果没有,请参阅Jakob Bowyer的解决方案,如果您的文件是二进制的,则使用file.read(SOME_BYTE_COUNT)变量。
  • 您已经在注释中说明了这一点,但为了完整起见:您希望实际编辑内容,而不仅仅是查看内容。

Long answer:由于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

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

时间:

这些到底有多重要?好吧,让我看看。我做了一个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有一个copy-on-write文件复制操作——使获取文件副本的行为几乎是即时的。使用任何文件系统的LVM快照也可以达到同样的效果。)

相关问题 更多 >