【问题标题】:fast data move from file to some StringIO快速数据从文件移动到某个 StringIO
【发布时间】:2011-11-23 10:30:08
【问题描述】:

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

但如果我这样做:

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

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

【问题讨论】:

  • 你打算用stream做什么?读了吗??
  • 您使用的是 Python 2.x 还是 3.x?
  • @JohnMachin:我也想阅读和修改它。这个问题是关于 Python 的一般问题,如果 2.x 和 3.x 之间有区别,请说

标签: python stream


【解决方案1】:

简而言之:使用 StringIO 无法避免 2 个副本。

一些假设:

  • 您正在使用 cStringIO,否则优化这么多会很愚蠢。
  • 您追求的是速度而不是内存效率。如果不是,请参阅 Jakob Bowyer 的解决方案,或者如果您的文件是二进制文件,请使用 file.read(SOME_BYTE_COUNT) 的变体。
  • 您已经在 cmets 中说明了这一点,但为了完整起见:您希望实际编辑内容,而不仅仅是查看它。

长答案:由于 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 "将 numpy 导入为 np" "a = np.fromfile('largefile.bin', 'uint8'); a[0] = 1" 10 个循环,3 个循环中的最佳:每个循环 132 毫秒 $ python -m timeit -s "从 cStringIO 导入 StringIO" "a = StringIO(); a.write(open('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 倍。当然,这在很大程度上取决于您要对文件做什么。改变第一个字节几乎没有代表性。但是使用这种方法,您确实可以在其他两个方面取得领先,并且由于大多数操作系统具有良好的磁盘缓冲,因此速度也可能非常好。

(如果您不允许编辑文件并希望避免制作工作副本的成本,有几种可能的方法可以提高速度。如果您可以选择文件系统,Btrfscopy-on-write 文件复制操作 -- 使复制文件的行为几乎是即时的。使用任何文件系统的 LVM 快照可以达到相同的效果。)

【讨论】:

  • 是否有没有numpy的意思,即在stdlib中?也许 bytearray 有同样的效果?
  • 我不知道,不。 Bytearray 似乎不接受文件对象作为参数。
  • 这听起来很可惜,所以从文件中快速读取可修改缓冲区的唯一方法是使用 numpy :(
  • 最快的当然是使用文件对象方法seekreadwrite直接编辑文件(或副本)——与StringIO相同的接口。磁盘缓存也将有助于加快速度。你有什么理由想要它在内存中吗?
  • 如果要对数据进行大量处理,我不确定将其保存在文件中会更快。另外,我不想修改文件本身的内容(但最终会生成一些报告到另一个文件)
【解决方案2】:

不,没有额外的副本。用于存储数据的缓冲区是相同的。 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() 时会复制,因此我无法重复上述演示。

【讨论】:

  • 由于data 的内容是不可变的,而stream 的内容不是,所以一旦修改了StringIO 对象,就必然会进行额外的复制,如果不是之前的话。问题依然存在。
  • 这是一个不同的问题。如果你想知道 StringIO 是如何工作的,最好阅读StringIO.py
  • @MichaelHoffman:谢谢,但我对修改完成后制作的副本特别感兴趣。我知道 StringIO 做到了,我的问题是如何避免 id。如何直接从文件中读取数据到可修改的StringIO?
【解决方案3】:

也许您正在寻找的是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 是可变的,因此不会创建额外的副本并且可以修改数据。

【讨论】:

  • 这取决于被访问的对象。在memoryview 文档中有一个示例可以更改bytearray 对象中的值(不更改其大小)。但是,在您的示例中,file.read 将返回一个不可变字符串 inmutable,因此您将无法在该对象上执行此操作。
  • 我刚刚在 reddit 中看到了this,它似乎解决了使用file.readinto 将数据放入bytearray 的问题。
【解决方案4】:
stream = StringIO()
for line in file:
    stream.write(line + "\n")

【讨论】:

    猜你喜欢
    • 2012-01-06
    • 1970-01-01
    • 1970-01-01
    • 2018-10-04
    • 2018-05-12
    • 1970-01-01
    • 1970-01-01
    • 2019-02-14
    • 1970-01-01
    相关资源
    最近更新 更多