ArrayList源码解析之序列化

引子

我们都知道 ArrayList 是通过 数组 来存储元素的。阅读 ArrayList 的源码,会发现:

css 复制代码
transient Object[] elementData;

也就是说,用来存放元素的数组 elementDatatransient 修饰了。按道理,transient 的意思是"这个字段不要被序列化",那么问题就来了------ArrayList 里的元素难道不需要被序列化吗?那它是怎么把数据存下来的?

带着这个疑问,我们继续往下看。


源码揭秘

首先确认一下,elementData 确实是 transient 的:

css 复制代码
transient Object[] elementData; 

那它的数据是怎么保存和恢复的呢?答案就是: ArrayList 自己实现了一套序列化和反序列化方法 ,也就是 writeObjectreadObject

自定义序列化

arduino 复制代码
@java.io.Serial
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException {
    int expectedModCount = modCount;
    // 先写入非 transient 字段,比如 size
    s.defaultWriteObject();
​
    // 写 size(元素数量),注意不是数组的容量
    s.writeInt(size);
​
    // 写入真正存储的元素
    for (int i = 0; i < size; i++) {
        s.writeObject(elementData[i]);
    }
​
    // 并发修改检测
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

自定义反序列化

arduino 复制代码
@java.io.Serial
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // 先恢复非 transient 字段
    s.defaultReadObject();  
​
    // capacity,这里直接忽略掉
    s.readInt(); 
​
    if (size > 0) {
        // 按 size 来分配数组,而不是之前的容量
        Object[] elements = new Object[size];
​
        // 把序列化时保存的元素一个个读回来
        for (int i = 0; i < size; i++) {
            elements[i] = s.readObject();
        }
​
        elementData = elements;
    } else if (size == 0) {
        elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new java.io.InvalidObjectException("Invalid size: " + size);
    }
}

实现解析

从代码可以看出,ArrayList 在序列化时只关心 真实存放的元素,而不是整个数组容量。

为什么这么做?举个例子:

ini 复制代码
ArrayList<String> list = new ArrayList<>(1000);
list.add("A");

这个时候,elementData 的数组长度可能是 1000,但实际上只存了一个元素 "A"。 如果不加 transient,直接把整个数组序列化,那 999 个 null 也会被写到文件里,结果就是 空间浪费 + 性能下降

所以,JDK 作者就用了一个取巧的办法:

  • elementData 标记为 transient,让它不参与默认序列化。
  • writeObject 里只把 size 个真实元素写出去。
  • readObject 时再用 size 来重建数组,把元素填回去。

这样既节省空间,也让 ArrayList 的序列化行为更合理。


总结

  1. ArrayList 的底层存储数组 elementDatatransient 修饰,不会直接参与序列化。

  2. ArrayList 自己实现了 writeObject / readObject,只序列化实际存储的元素,而不是整个数组容量。

  3. 这样做的好处是:

    • 避免序列化过程中保存大量无效的 null
    • 提升序列化和反序列化效率,节省存储空间。
    • ArrayList 在不同 JDK 版本的扩容策略下,依然保持序列化兼容性。

一句话总结:ArrayList 的序列化是"只保存有用的元素,不浪费空间"

相关推荐
凤凰院凶涛QAQ1 小时前
《Java版数据结构 & 集合类剖析》栈与队列:“push/pop 是栈的灵魂,offer/poll 是队列的骨架——四组 API,两种人生”
java·开发语言·数据结构
郝学胜-神的一滴2 小时前
算法实战:最小k个数——大顶堆的优雅解法
开发语言·数据结构·c++·python·程序人生·算法·排序算法
Irissgwe2 小时前
算法滑动窗口
数据结构·算法
掘金_答案2 小时前
上线那天,一个 ConcurrentHashMap 差点送走我的 AI 客服——3 天排查 JVM 血泪史
java·后端·架构
猿与禅3 小时前
CosId 分布式 ID 生成器完全教程:从架构原理到生产落地
java·shardingsphere·雪花算法·分布式id·高性能·cosid·号段模式
MindUp3 小时前
企业网盘权限模型解析:多层级访问控制与审计能力选型指南
java·linux·运维
NG4773 小时前
【软件设计与体系结构】-策略设计模式
java·设计模式·软件工程
程序员天天困4 小时前
优雅记录操作日志:从注解到 SpEL 的全链路实践与开源方案对比
java·后端
战血石LoveYY4 小时前
Integer类型超限,导致数据异常
java·算法
乐观的Terry5 小时前
1、为什么要自己造发布系统
java·spring boot·后端·spring cloud·架构