在 Python 中将重复字节流附加到 IO

Appending a stream of repeated bytes to IO in Python

提问人:trapdrap 提问时间:11/11/2023 最后编辑:trapdrap 更新时间:11/11/2023 访问量:54

问:

我正在编写一个程序,该程序需要在 Python 中将大量重复字节写入文件。

为了简化问题,我将使用数据的表示形式。 始终是 的返回结果,并将是“重复数据”的任意表示bufferopen(file_name, "a+")b"0"

假设我想将重复的数据写入 50,000 次,到目前为止,我已经想到了 3 种方法。

第一种方法是先在内存中生成数据,然后再写入(这会占用大量内存)

data = b"0" * 50_000
buffer.write(data)

第二种方法是将每次迭代都写在一个循环中(这非常慢)

for _ in range(50_000):
    buffer.write(b"0")

最后,第三种方法是将两者结合起来,并编写更大的片段(这比第二种选择快得多,但比第一种慢,最重要的是,即使在使用时也会使用内存,而且总的来说,这种设计非常丑陋)

data_x1 = b"0"
data_x10 = b"0" * 10
data_x100 = b"0" * 100
data_x1000 = b"0" * 1000

# writing in a loop using bigger to smaller segments
# until there is no more to write
num_left = 50_000
while num_left > 0:
    if num_left >= 1000:
        buffer.write(data_x1000)
        num_left -= 1000
    elif num_left >= 100:
        buffer.write(data_x100)
        num_left -= 100
    elif num_left >= 10:
        buffer.write(data_x10)
        num_left -= 10
    else:
        buffer.write(data_x1)
        num_left -= 1

TLDR:目标是在不使用 Python 循环的情况下将一组指定的字节重复写入文件,也不首先在内存中生成整个序列。

我一直在研究 的 write 方法,并注意到它需要一个类似字节的对象。 如果可能的话,最佳方法是能够创建一个“类似字节”的对象来模拟重复数据流,缓冲区可以在不使用 Python 循环的情况下写入 x 次。BufferedWriter

蟒蛇 python-3.x io

评论

0赞 Community 11/11/2023
请澄清您的具体问题或提供其他详细信息以准确说明您的需求。正如目前所写的那样,很难确切地说出你在问什么。
0赞 trapdrap 11/11/2023
我试图通过使问题更加直接来解决上述问题。
0赞 Kelly Bundy 11/11/2023
你的第三条道路之所以丑陋,只是因为你让它变得丑陋。我会结合前两种方式,但要好得多。
0赞 ti7 11/27/2023
你有没有设法在这里找到一个好的解决方案或对任何其他人进行基准测试?
0赞 trapdrap 11/28/2023
@ti7我选择使用您的解决方案,但我不相信有一种有效的方法可以实现它,否则无需花费大量精力在 C 中进行文件编写,并且由于它只创建一个内存视图,因此它不是一个显着的上升气流,唯一的缺点是预分配内存。感谢您的回复:)

答:

0赞 ti7 11/11/2023 #1

只需尽可能使最大的块,并将最后一个写入切成小块

# largest block you can put into memory
# replicate block until some limit
# must wrap at end if data isn't all the same
data = b"0" * 50_000

added_length = 120_000

with open(file_name, "ab+") as fh:  # bytes mode makes math work
    while fh.tell() < added_length:
        fh.write(data[:added_length - fh.tell()])  # don't write too much

请注意,打开时,将从 开始,并且是初始文件结尾的偏移量。但是,如果您想同时阅读其他内容或获取总文件大小,您可能会发现打开和结束会更好"a".tell()0"r+".seek()

评论

0赞 Frank Yellin 11/11/2023
为什么使用 .只需使用 .然后你写时间,然后写一个截断的字节来完成。fh.tell()q, r = divmod(added_length, len(data))dataqr
0赞 ti7 11/11/2023
啊,它们将得到相同的结果,无论哪种情况的清晰度都应该在这里真正占主导地位 - 合理地写正好 N 次会更强大,但我预计如果有什么严重错误的话!IOError
0赞 trapdrap 11/11/2023
这看起来要优雅得多,尽管截断字节会将其创建为内存中的新对象,这反过来又会占用大量内存。最初实现 3 的目标是减少迭代次数,同时减少内存使用量。
0赞 ti7 11/11/2023
切片不应创建新对象,而应创建视图
0赞 trapdrap 11/12/2023
这可能是我想要的更好的解决方案,尽管如果我不必拥有预生成的块,因为它是一项可选功能,那仍然是最好的,我想也许有一种方法可以创建一个较低级别的模块,该模块可以在不移动指针的情况下连续将内存的一部分写入文件
0赞 Frank Yellin 11/11/2023 #2

如果您打算尝试使用大型缓冲区进行写入,则最好使用系统默认缓冲区大小。

import io
data = b'0' * io.DEFAULT_BUFFER_SIZE
full, extra = divmod(total_bytes_to_write, io.DEFAULT_BUFFER_SIZE)
with open(file_name, "ab+") as fh:
    for _ in range(full):
        fh.write(data)
    fh.write(data[:extra]

通过使用默认缓冲区大小的倍数,可能会获得一点收益,但请务必在命令中也使用此值。open

import io
buffer_size = 4 * io.DEFAULT_BUFFER_SIZE
data = b'0' * buffer_size
full, extra = divmod(total_bytes_to_write, buffer+size)
with open(file_name, "ab+", buffering=buffer_size) as fh:
    .. same as above ...

用户@trapdrap(见注释)声称,在所有情况下,您都应该在所有情况下使用,因为您不希望或不需要缓冲。buffering = 0

评论

0赞 trapdrap 11/11/2023
使用参数可以使内存中的对象一次性转储所有对象,这也是一个巨大的内存问题。在不缓冲的情况下写入 75mb 会使用 400kb 的内存,而使用 75mb 的缓冲会使用 80mb 的内存,并且这会在所有同时写入的实例中重复buffering
0赞 Frank Yellin 11/11/2023
@trapdrap。所以你认为它应该在所有情况下都是?为此添加了注释。buffering = 0