17370845950

bytes 和 bytearray 的 append/insert 操作性能对比与内存拷贝情况
bytes不可变,无append和insert方法,调用会报AttributeError;bytearray可变,append均摊O(1),insert触发局部内存移动,扩容策略为比例增长。

bytes 的 append 和 insert 操作根本不存在

bytes 是不可变类型,任何看似“追加”或“插入”的操作(比如 +=b + b2bytes.insert())都会触发新对象创建。所谓 bytes.append() 会直接报 AttributeError: 'bytes' object has no attribute 'append';同理 insert 方法也不存在。这不是性能问题,是语法错误——你连调用都调不通。

常见误操作包括:

  • bytes 当成 bytearray 用,结果在运行时崩掉
  • += 拼接小段 bytes,误以为是原地修改,实际每次都在分配新内存并拷贝全部字节
  • 在循环里反复 b = b + new_part,时间复杂度退化为 O(n²),尤其当总长度增长到几 MB 以上时明显卡顿

bytearray.append() 是真正的 O(1) 均摊操作

bytearray 是可变序列,append() 在底层使用动态数组策略:预分配额外空间,避免每次插入都 realloc。只要未触及容量上限,单次 append() 不触发整块内存拷贝。

实测要点:

  • 初始 bytearray() 容量通常为 0,首次 append 后会分配约 16 字节;后续扩容按比例增长(CPython 中约为 1.125 倍)
  • 大量追加时,总拷贝次数远少于追加次数——例如追加 1000 个字节,实际可能只发生 5–8 次 realloc + memcpy
  • 若提前知道最终大小,用 bytearray(n) 预分配再逐字节赋值(ba[i] = x),可彻底避免扩容拷贝

示例对比:

ba = bytearray()
for i in range(1000):
    ba.append(i & 0xFF)  # 快,均摊 O(1)

b = b'' for i in range(1000): b += bytes([i & 0xFF]) # 慢,O(n²),每轮都复制前面所有字节

bytearray.insert() 触发局部内存移动,代价明确

bytearray.insert(i, x) 必须将索引 i 及之后的所有字节向后平移一位,本质是 memmove(dst=i+1, src=i, n=len-i)。它不涉及 realloc(除非容量不足),但移动成本与插入位置后的长度成正比。

关键事实:

  • 在开头 insert(0, x) 最慢:需移动全部现有数据
  • 在末尾 insert(len(ba), x) 等价于 append(),不移动数据,仅检查容量
  • 频繁中间插入应改用 list[bytes] 缓存再 b''.join(),或改用 io.BytesIO(其 write() 是追加语义,getbuffer() 可零拷贝转 bytearray

内存拷贝是否发生,看底层 PyBufferProcs 和 ob_size/allocated

判断一次操作是否拷贝内存,不能只看函数名,得看 CPython 源码中对应方法是否调用了 PyMem_Reallocmemmove。比如:

  • bytearray.extend(iterable):若容量足

    够,直接 memcpy 新数据到尾部;否则先 realloc 再 memcpy
  • del ba[i:j]:不 realloc,只 memmove 后续数据覆盖被删区域
  • ba[:] = other:若 len(other) ,就地覆盖;否则先 realloc

真正难察觉的坑是:某些看似“安全”的操作(如切片赋值 ba[10:10] = b'xyz')在容量不足时仍会 realloc —— 这时候你以为只是插几个字节,实际背后发生了整块拷贝。