`open`和`io.BytesIO`在二进制流中的区别

133
我正在学习Python中流的处理,我注意到IO文档中提到:

创建二进制流最简单的方法是在打开模式字符串时使用' b ':

f = open("myfile.jpg", "rb")

内存中的二进制流也可以作为BytesIO对象使用:

f = io.BytesIO(b"some initial binary data: \x00\x01")

通过 open 定义的 f 和通过 BytesIO 定义的 f 有何区别?换句话说,“内存中的二进制流”是什么,与 open 的操作有何不同?

2个回答

185

为了简单起见,现在让我们只考虑写作,而不是阅读。

所以当你使用 open() 像这样说:

with open("test.dat", "wb") as f:
    f.write(b"Hello World")
    f.write(b"Hello World")
    f.write(b"Hello World")
执行这个操作后,将创建一个名为test.dat的文件,其中包含3个Hello World。数据在写入文件后不会保留在内存中(除非由名称保存)。现在,如果您考虑使用io.BytesIO()
with io.BytesIO() as f:
    f.write(b"Hello World")
    f.write(b"Hello World")
    f.write(b"Hello World")

这段代码不是将内容写入文件,而是写入内存缓冲区,也就是一块RAM。换句话说,下面的代码与之等价:

buffer = b""
buffer += b"Hello World"
buffer += b"Hello World"
buffer += b"Hello World"

关于with语句的示例,最后也需要加上del buffer

这里的关键区别在于优化和性能。io.BytesIO 能够进行一些优化,使其比简单地逐个连接所有的 b"Hello World" 更快。

只是为了证明这一点,以下是一个小型基准测试:

  • 连接操作:1.3529秒
  • BytesIO:0.0090秒

import io
import time

begin = time.time()
buffer = b""
for i in range(0, 50000):
    buffer += b"Hello World"
end = time.time()
seconds = end - begin
print("Concat:", seconds)

begin = time.time()
buffer = io.BytesIO()
for i in range(0, 50000):
    buffer.write(b"Hello World")
end = time.time()
seconds = end - begin
print("BytesIO:", seconds)
除了性能提升之外,使用BytesIO替换拼接的优点在于BytesIO可以用作文件对象的替代品。因此,假设您有一个期望写入文件对象的函数。那么您可以将其内存缓冲区传递给该函数,而不是一个文件。

区别在于open(“myfile.jpg”,“rb”)仅加载并返回myfile.jpg的内容;而BytesIO只是包含一些数据的缓冲区。

由于BytesIO只是一个缓冲区,如果您想稍后将其内容写入文件,则必须执行以下操作:

buffer = io.BytesIO()
# ...
with open("test.dat", "wb") as f:
    f.write(buffer.getvalue())

此外,您没有提到版本;我正在使用Python 3。关于示例:我使用的是with语句而不是调用f.close()


7
非常好的回答;问题提到了“内存流(in memory stream)”,而你却用了“内存缓冲区(in memory buffer)”这个词。在Python中有区别吗?简要讨论一下会很有价值。从英语语义角度来看,“stream”暗示着从源头到目标的连续比特流动(从源头推送),而“buffer”则意味着在源头缓存比特以便快速从源头获取块或片段(目标从源头拉取)。 - Davos
我在我的机器上运行了小型基准测试,并使用Python3.5获得了类似的结果,但是当我使用Python 2.7时,“Concat”和“BytesIO”花费的时间相似,“Concat”甚至稍微好一些。有什么问题吗?这让我感到困惑。 - JenkinsY
@Vallentin,抱歉,您说“open(“myfile.jpg”,“rb”)只是加载并返回myfile.jpg的内容”是错误的,请看这个`import io f = open("myfile.jpg", "rb") <class '_io.BufferedReader'>
isinstance(f, io.BufferedIOBase) True `
- Yahya Yahyaoui
很棒的答案,很高兴你甚至涵盖了如何根据需要将缓冲区写回文件! - Elias Yishak

34

使用 open 函数可以打开硬盘上的文件。根据使用的模式,您可以从磁盘读取或写入(或同时进行读写)。

BytesIO 对象不与磁盘上的任何实际文件相关联,它只是一块行为类似于文件的内存块。它具有与从 open 返回的文件对象相同的 API(使用模式 r+b,允许读取和写入二进制数据)。

BytesIO(以及始终处于文本模式的近亲 StringIO)在需要传递给期望得到文件对象的 API 但您希望直接传递数据时很有用。您可以在将数据传递给库之前将输入数据加载到 BytesIO 中。在函数返回后,您可以使用 getvalue() 方法从 BytesIO 中获取库写入文件的任何数据。当然,通常您只需要执行其中一个操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接