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

Posted

技术标签:

【中文标题】二进制流中`open`和`io.BytesIO`之间的区别【英文标题】:Difference between `open` and `io.BytesIO` in binary streams 【发布时间】:2017-08-05 15:08:03 【问题描述】:

我正在学习如何在 Python 中使用流,我注意到 IO docs 说了以下内容:

创建二进制流的最简单方法是在模式字符串中使用带有“b”的 open():

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

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

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

open 定义的fBytesIO 定义的f 有什么区别。换句话说,是什么构成了“内存中的二进制流”,它与 open 所做的有什么不同?

【问题讨论】:

【参考方案1】:

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

所以当你使用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 的文件,其中包含 3x 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()

【讨论】:

很好的答案;问题提到了in memory stream,而您提到了in memory buffer。 Python有区别吗?值得简要介绍一下。从英语语义的角度来看,stream 意味着从源到接收器的连续位流(从源推送),其中缓冲区意味着源中的位缓存,准备好从源快速获取块或片段(接收器拉来源)。 我在我的机器上运行了小基准测试并使用 Python3.5 得到了类似的结果,但是,当我使用 Python 2.7 时,“Concat”和“BytesIO”需要相似的时间,“Concat”是偶数稍微好一些。哪里不对了?这让我很困惑。 @Vallentin,对不起,当你说“open("myfile.jpg", "rb") 只是加载并返回 myfile.jpg 的内容时你错了,见 rhis import io f = open("myfile.jpg", "rb") <class '_io.BufferedReader'> >>> isinstance(f, io.BufferedIOBase) True 【参考方案2】:

使用open 在您的硬盘上打开一个文件。根据您使用的模式,您可以从磁盘读取或写入(或两者兼而有之)。

BytesIO 对象不与磁盘上的任何真实文件相关联。它只是一块内存,其行为就像文件一样。它与从open 返回的文件对象具有相同的API(模式为r+b,允许读写二进制数据)。

BytesIO(它是近亲 StringIO,始终处于文本模式)在您需要将数据传入或传出希望获得文件对象但您更喜欢的 API 的 API 时会很有用直接传递数据。您可以将您拥有的输入数据加载到 BytesIO 中,然后再将其提供给库。返回后,您可以使用getvalue() 方法从BytesIO 获取库写入文件的任何数据。 (当然,通常您只需要执行其中一项即可。)

【讨论】:

以上是关于二进制流中`open`和`io.BytesIO`之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

pythonio.BytesIO简要介绍及示例

python wave 库 读取 BytesIO 对象的注意事项

PIL(Pillow)的学习笔记

如何测试使用图像的 FastAPI api 端点?

将 io.BytesIO 转换为 io.StringIO 以解析 HTML 页面

在 python3 中写入 csv 中的 io.BytesIO 失败