在Python中我有一个文件流,我想将其中的一部分复制到StringIO
。我希望尽可能快地使用最少的副本。
但如果我这样做:
data = file.read(SIZE)
stream = StringIO(data)
我认为已完成2份,不是吗?从文件中复制一份数据,将StringIO
内的另一份复制到内部缓冲区。我可以避免其中一个副本吗?我不需要临时data
,所以我认为一份副本应该足够了
答案 0 :(得分:8)
简而言之:使用StringIO无法避免2份副本。
一些假设:
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.insert
,np.delete
和np.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")