【一起读源码】ArrayList中的elementData是用transient的修饰!和反序列化有关?

248 阅读3分钟

前言

作为代码开发中最最常用的一个集合ArrayList,今天我在摸鱼的时候不小心点进源码。我们都知道ArrayList是基于数组实现的。然后我注意到它存放元素的容器 Object[] elementData 是用transient修饰的。带这个疑问,就有了下面的内容。

源码分析

我们发现ArrayList的核心存放数据的数组 elementData 是以 transient 修饰的,该修饰符声明的内容不会被序列化。

image.png

transient是什么?序列化又是用来干啥的?

  • transient翻译是短暂的意思。对于transient修饰的成员变量,在对象的生命周期中,只会存在于内存中,而不会写入到磁盘中。
  • 所谓的序列化就是把对象转变为可读的字节码文件,字节码文件才能被字节码执行引擎执行,最终存在JVM中(也就是将整个对象的定义和数据持久化下来)

保存元素会丢失?看看readObject和writeObject

那么在序列化后,ArrayList里面的元素数组保存的数据不就完全丢失了吗?

其实并不会,ArrayList提供了两个类似重写用于序列化的方法:readObject和writeObject。这两个方法在序列化的时候会被调用,我们重点看下writeObject方法,该方法有两个重点的地方:1. 遍历真正数组元素写入对象;2. 写入完成后用modCount查看与写入之前是否一致

  • writeObject

image.png

  • readObject

image.png

  • modCount (该属性在ArrayList每次修改的时候都会计数一次,可以用改属性判断最终一致性)例如在每次add方法或者remove等方法调用时都会调用modCount++计数:

image.png

小结: 这个时候我们就能发现了,在writeObject方法中处理是为了减少不必要的空元素。因为ArrayList在扩容之后,有的时候实际存放的元素并没有那么多,以1.5倍进行扩容,长度没有满的时候,还会存很多空元素,这些元素也是十分占空间的。

拓展:序列化的时候是如何调用到writeObject和readObject?

事实上集合框架中大部分集合都是用重写writeObject和readObject方法的。有hashMap等等集合

我们发现这两个方法的修饰符都是private,都是私有的那么它们在序列化的时候都是怎么调用到这些方法的呢?

其实不难想出,能调用到私有方法的,通常都是利用反射的。有兴趣的同学可以再去看一下ObjectInputStream方法。在序列化的时候会去反射调用readObject方法的。

image.png

总结

最终原因是在扩容的时候,ArrayList是以1.5倍长度扩容的,这样数组会有很多空值元素,会占用大量空间,所以重写的方法由遍历存储数据,就会只序列化不是空值的部分,以节省空间和时间