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

相关推荐
CYRUS_STUDIO4 小时前
一步步带你移植 FART 到 Android 10,实现自动化脱壳
android·java·逆向
练习时长一年4 小时前
Spring代理的特点
java·前端·spring
CYRUS_STUDIO4 小时前
FART 主动调用组件深度解析:破解 ART 下函数抽取壳的终极武器
android·java·逆向
MisterZhang6664 小时前
Java使用apache.commons.math3的DBSCAN实现自动聚类
java·人工智能·机器学习·自然语言处理·nlp·聚类
Swift社区5 小时前
Java 常见异常系列:ClassNotFoundException 类找不到
java·开发语言
Li_yizYa5 小时前
List | 常见的List实现类(ArrayList、LinkedList、Vector)以及ArrayList源码解读
数据结构·list
一只叫煤球的猫6 小时前
怎么这么多StringUtils——Apache、Spring、Hutool全面对比
java·后端·性能优化
维基框架7 小时前
维基框架 (Wiki FW) v1.1.1 | 企业级微服务开发框架
java·架构
某空_7 小时前
【Android】BottomSheet
java
10km7 小时前
jsqlparser(六):TablesNamesFinder 深度解析与 SQL 格式化实现
java·数据库·sql·jsqlparser