HashMap序列化时需重写writeObject/readObject方法,仅遍历并序列化非空桶中的节点,避免默认机制序列化整个table数组;其table字段本就是transient,无需重复声明。

HashMap 序列化时默认会序列化整个 table 数组(包括空桶),但实际有效数据只占一部分。要跳过空桶、只序列化真正存储了键值对的桶,不能依赖默认的 Serializable 机制——它会把整个数组(含 null 元素)一并写入,造成空间浪费和反序列化冗余。
自定义 writeObject/readObject 方法
这是最直接、可控的方式。HashMap 本身已预留了该机制:重写私有方法 writeObject 和 readObject,在序列化前遍历 table,仅写出非空桶中的节点链或树结构。
- 在
writeObject中,先调用defaultWriteObject()序列化非 transient 字段(如size、modCount等) - 再遍历
table数组,对每个非null桶,递归写出其链表或红黑树的所有节点(包括 hash、key、value、next 或 left/right/parent 等必要字段) - 在
readObject中,先defaultReadObject(),再根据读出的节点数据重建table,手动触发putVal或直接构造 Node 并插入对应桶位
使用 transient 修饰 table 并手动管理
table 字段本身就是 transient 的(JDK 源码中已声明),这意味着它不会被默认序列化。真正的序列化逻辑全由 writeObject 承担——所以你无需额外加 transient,只需确保自定义的 writeObject 不遍历空桶即可。
- 注意:不要在类中新增
transient Node[] table,这会覆盖 JDK 原有声明,导致反序列化失败 - 重点是复用原有逻辑框架,在遍历时加判空:
if (tab[i] != null) { ... }
避免序列化冗余桶的实践要点
即使不改源码,也能通过控制初始化参数减少空桶比例,间接提升序列化效率:
立即学习“Java免费学习笔记(深入)”;
- 构造时指定合理初始容量(如预估 size / 0.75 向上取 2 的幂),避免早期频繁扩容产生大量空桶
- 序列化前调用
trimToSize()(需自行实现或反射访问 resize),但注意这会触发 rehash,慎用于高频场景 - 若业务允许,可转为
LinkedHashMap或TreeMap后再序列化——它们内部不维护稀疏数组,但会牺牲 O(1) 查找性能
本质上,跳过空桶不是“配置开关”,而是通过重写序列化钩子函数实现的数据精简。JDK 自身的 HashMap 已按此思路实现,你继承或模仿其 writeObject 行为即可精准控制输出内容。


















