bitarray比list[int]省内存是因为它用1个bit存储1位数据,而list[int]每个元素需存储PyObject*指针(8字节)及int对象(≥28字节);100万布尔值仅占约125KB,压缩率超100倍。

bitarray 为什么比 list[int] 节省内存
Python 的 list 存整数,每个元素本质是 PyObject* 指针(64 位系统下占 8 字节),外加 int 对象本身的开销(至少 28 字节)。哪怕只是存 0/1,也得用完整 int 对象。而 bitarray 把比特直接压进字节数组,1 个 bit 就存 1 位 —— 100 万个布尔值只占约 125 KB,对比原生 list[bool](≈8 MB)或 list[int](≈16 MB+),压缩率超 100 倍。
注意:bitarray 不是 Python 标准库,需 pip install bitarray;它不支持负数索引的 slice 反向截取(如 a[-5:] 会报 NotImplementedError),也不支持直接传入非 0/1 的整数初始化(比如 bitarray([2, 3]) 会静默转成 [0, 1],容易误判)。
如何把已有整数列表安全转成 bitarray
核心原则:先明确业务语义 —— 这些整数是不是真能映射为单个比特?常见场景是“存在性标记”(比如用户 ID 是否在黑名单中)、“开关状态”(第 i 位表示第 i 个任务是否完成)。
若原始列表是纯 0/1 整数,最简转换:
立即学习“Python免费学习笔记(深入)”;
from bitarray import bitarray ints = [1, 0, 0, 1, 1, 0] ba = bitarray(ints) # ✅ 直接接受 list[int],自动过滤非 0/1 值
但若原始数据含任意整数(如用户 ID 列表),不能直接喂给 bitarray()。应先做归一化:
- 若需记录「哪些 ID 出现在集合中」,且 ID 范围已知(如 0~999999),用
bitarray('0') * N初始化,再按 ID 置位:ba[user_id] = 1 - 若 ID 稀疏且范围极大(如 64 位用户 ID),
bitarray不适用,该换set或布隆过滤器 - 避免用
bitarray([x & 1 for x in huge_list])—— 生成中间 list 会瞬间吃光内存,改用生成器:bitarray(x & 1 for x in huge_list)
访问与修改时的性能陷阱
bitarray 支持 ba[i]、ba[i:j]、ba.append(),但行为和 list 不完全一致:
常见坑点:
-
ba[123] = 2不报错,但等价于ba[123] = True(即设为 1),2 会被隐式转为True→ 丢失原始值含义 -
ba.extend([1,0,1])是高效操作(C 层批量写入);但for x in data: ba.append(x)是 Python 循环调用,慢 10 倍以上 -
ba.tobytes()返回 bytes,可用于序列化;但ba.tolist()会重建 Python int 列表,彻底失去内存优势,仅调试时用 - 多线程读写需手动加锁 ——
bitarray本身不是线程安全的
替代方案对比:什么时候不该用 bitarray
bitarray 只适合「单比特状态」场景。如果需求稍复杂,立刻露馅:
例如:
- 要存 -1 / 0 / 1 三态?
bitarray无法表达,得用array.array('b')或numpy.int8 - 要频繁按值查找(如“找出所有为 1 的位置”)?
ba.itersearch(bitarray('1'))可用,但比numpy.where(arr == 1)慢且 API 别扭 - 要做位运算(如 &、|)但操作数长度不等?
bitarray要求严格等长,否则抛ValueError;而numpy.ndarray会自动广播 - 数据要持久化到磁盘且需跨语言读取?
bitarray.tobytes()是裸字节,没元信息,不如 Protocol Buffers 或 Arrow 格式鲁棒
真正省内存的前提,是你确认自己只需要 1 bit 表达一个事实,并且整个生命周期都围绕这个事实做增删查改。一旦开始想“能不能再塞个计数”“要不要支持负数”,就该停下来重选工具了。


















