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 的序列化是"只保存有用的元素,不浪费空间"

相关推荐
栈与堆33 分钟前
LeetCode 19 - 删除链表的倒数第N个节点
java·开发语言·数据结构·python·算法·leetcode·链表
一路向北·重庆分伦35 分钟前
03-01:MQ常见问题梳理
java·开发语言
一 乐36 分钟前
绿色农产品销售|基于springboot + vue绿色农产品销售系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·宠物
lhrimperial42 分钟前
企业智能知识库助手落地实践:从RAG到Multi-Agent
java·spring cloud·微服务·系统架构·知识图谱
3***68841 小时前
Spring Boot中使用Server-Sent Events (SSE) 实现实时数据推送教程
java·spring boot·后端
-森屿安年-1 小时前
unordered_map 和 unordered_set 的实现
数据结构·c++·散列表
C***u1761 小时前
Spring Boot问题总结
java·spring boot·后端
Elieal1 小时前
5 种方式快速创建 SpringBoot 项目
java·spring boot·后端
better_liang1 小时前
每日Java面试场景题知识点之-Java修饰符
java·访问控制·static·abstract·final·修饰符·企业级开发
rgeshfgreh1 小时前
Spring事务传播机制深度解析
java·前端·数据库