Han*_*agi 4 io python-3.x python-3.7
从文档
buffering 是一个可选整数,用于设置缓冲策略。传递 0 以关闭缓冲(仅在二进制模式下允许),1 以选择行缓冲(仅可用于文本模式),以及一个大于 1 的整数以指示固定大小块缓冲区的大小(以字节为单位)。当没有给出缓冲参数时,默认缓冲策略的工作方式如下:
二进制文件以固定大小的块缓冲;缓冲区的大小是通过尝试确定底层设备的“块大小”并回退到 io.DEFAULT_BUFFER_SIZE 的启发式方法来选择的。在许多系统上,缓冲区通常为 4096 或 8192 字节长。“交互式”文本文件( isatty() 返回 True 的文件)使用行缓冲。其他文本文件使用上述针对二进制文件的策略。
我打开一个以test.log文本模式命名的文件,并将缓冲设置为 16。所以我认为块大小是 16,当我将 32 个字节的字符串写入文件时。它会调用write(syscall) 两次。但实际上,它只调用一次。(在 Linux 上的 Python 3.7.2 GCC 8.2.1 20181127 中测试)
import os
try:
    os.unlink('test.log')
except Exception:
    pass
with open('test.log', 'a', buffering=16) as f:
    for _ in range(10):
        f.write('a' * 32)
使用strace -e write python3 test.py以跟踪系统调用,并获得以下
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 320) = 320
是什么buffering意思?
此答案对 CPython 3.7 有效,其他 Python 实现可能有所不同。
open()文本模式下的函数返回_io.TextIOWrapper(). 它_io.TextIOWrapper()具有内部“缓冲区” pending_bytes,其大小为 8192 字节(它是硬编码的),并且它还具有_io.BufferedWriter()用于文本模式w或_io.BufferedRandom()文本模式的句柄a。_io.BufferedWriter()/的大小_io.BufferedRandom()由函数buffering中的参数指定open()。
当您调用_io.TextIOWrapper().write("some text")它时,会将文本添加到内部pending_bytes缓冲区中。一些写入后,您将填充pending_bytes缓冲区,然后将其写入缓冲区内_io.BufferedWriter()。当您也填满里面的缓冲区时_io.BufferedWriter(),它将被写入目标文件。
当您以二进制模式打开文件时,您将直接从参数中获得使用缓冲区大小初始化的_io.BufferedWriter()/_io.BufferedRandom()对象buffering。
让我们看一些例子。我将从使用二进制模式的更简单的开始。
# Case 1
with open('test.log', 'wb', buffering=16) as f:
    for _ in range(5):
        f.write(b'a'*15)
跟踪输出:
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
在第一次迭代中,它用 15 个字节填充缓冲区。在第二次迭代中,它发现再添加 15 个字节会使缓冲区溢出,因此它首先刷新它(调用 system write),然后保存那些新的 15 个字节。在下一次迭代中,同样的情况再次发生。在缓冲区中的最后一次迭代之后是 15 B,它们在文件关闭时写入(离开with上下文)。
第二种情况,我将尝试将比缓冲区大小更多的数据写入缓冲区:
# Case 2
with open('test.log', 'wb', buffering=16) as f:
    for _ in range(5):
        f.write(b'a'*17) 
跟踪输出:
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
这里发生的情况是,在第一次迭代中,它将尝试写入缓冲区 17 B,但它无法容纳在那里,因此它被直接写入文件并且缓冲区保持为空。这适用于每次迭代。
现在让我们看看文本模式。
# Case 3
with open('test.log', 'w', buffering=16) as f:
    for _ in range(5):
        f.write('a'*8192)
跟踪输出:
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192) = 8192
首先回忆pending_bytes具有尺寸8192 B.在第一次迭代将其写入8192个字节(从代码:'a'*8192)到pending_bytes缓冲液中。在第二次迭代中,它添加到pending_buffer另一个 8192 字节并发现它大于 8192(pending_bytes缓冲区大小)并将其写入底层_io.BufferedWriter(). 缓冲区的  _io.BufferedWriter()大小为 16 B(buffering参数),因此它将立即写入文件(与情况 2 相同)。现在pending_buffer是空的,在第三次迭代中它再次填充了 8192 B。在第四次迭代中,它添加了另一个 8192 Bpending_bytes缓冲区溢出,并再次像第二次迭代一样直接写入文件。在最后一次迭代中,它将 8192 B 添加到pending_bytes缓冲区中,该缓冲区在文件关闭时刷新。
最后一个示例包含大于 8192 B 的缓冲。另外为了更好的解释,我又添加了 2 个迭代。
# Case 4
with open('test.log', 'w', buffering=30000) as f:
    for _ in range(7):
        f.write('a'*8192)
跟踪输出:
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 24576) = 24576
迭代:
pending_bytes.pending_bytes但它大于最大大小,因此它被写入底层_io.BufferedWritter()并保持在那里(pending_bytes现在是空的)。pending_bytes.pending_bytes但它大于最大大小,因此它尝试写入底层_io.BufferedWritter(). 但它会超过底层缓冲区原因的最大容量16384 + 16384 > 30000(第一个 16384 B 从迭代 2 开始仍然存在)所以它首先将旧的 16384 B 写入文件,然后将那些新的 16384 B(来自pending_bytes)放入缓冲区。(现在pending_bytes缓冲区再次为空)pending_buffer是空的,_io.BufferedWritter()包含 16384 B。在这个迭代中,它填充pending_buffer了 8192 B。就是这样。当程序离开with部分时,它会关闭文件。关闭过程如下:
pending_bufferinto写入_io.BufferedWriter()(可能是由于8192 + 16384 < 30000)8192 + 16384=) 24576 B 写入文件。顺便说一句,目前我不知道为什么pending_buffer它可以用于缓冲来自_io.BufferedWritter(). 我最好的猜测是它在那里,因为它提高了在文本模式下工作的文件的性能。
| 归档时间: | 
 | 
| 查看次数: | 2394 次 | 
| 最近记录: |