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

相关推荐
沧澜sincerely36 分钟前
Redis 键空间 & 五大类型
java·redis·docker
2351638 分钟前
【LeetCode】3. 无重复字符的最长子串
java·后端·算法·leetcode·职场和发展
nandao15840 分钟前
springBoot 集成Neo4j 实战演示
java·spring boot·neo4j
零雲1 小时前
java面试:可以讲一讲sychronized和ReentrantLock的异同点吗
java·开发语言·面试
没有bug.的程序员1 小时前
SQL 执行计划解析:从 EXPLAIN 到性能优化的完整指南
java·数据库·sql·性能优化·explain·执行计划
JasmineX-11 小时前
数据结构——静态链表(c语言笔记)
c语言·数据结构·链表
微笑尅乐1 小时前
神奇的位运算——力扣136.只出现一次的数字
java·算法·leetcode·职场和发展
Chan162 小时前
【 设计模式 | 结构型模式 代理模式 】
java·spring boot·后端·设计模式·intellij-idea
柯南二号2 小时前
【AI】【Java后端】RAG 实战示例:SpringBoot + 向量检索 + LLM 问答系统
java·人工智能·spring boot
Mr.Pascal2 小时前
后端直接返回错误信息的Map 和 抛出异常(异常机制)优劣势对比
java·springboot