前言
作为代码开发中最最常用的一个集合ArrayList,今天我在摸鱼的时候不小心点进源码。我们都知道ArrayList是基于数组实现的。然后我注意到它存放元素的容器 Object[] elementData 是用transient修饰的。带这个疑问,就有了下面的内容。
源码分析
我们发现ArrayList的核心存放数据的数组 elementData 是以 transient 修饰的,该修饰符声明的内容不会被序列化。
transient是什么?序列化又是用来干啥的?
- transient翻译是短暂的意思。对于transient修饰的成员变量,在对象的生命周期中,只会存在于内存中,而不会写入到磁盘中。
- 所谓的序列化就是把对象转变为可读的字节码文件,字节码文件才能被字节码执行引擎执行,最终存在JVM中(也就是将整个对象的定义和数据持久化下来)
保存元素会丢失?看看readObject和writeObject
那么在序列化后,ArrayList里面的元素数组保存的数据不就完全丢失了吗?
其实并不会,ArrayList提供了两个类似重写用于序列化的方法:readObject和writeObject。这两个方法在序列化的时候会被调用,我们重点看下writeObject方法,该方法有两个重点的地方:1. 遍历真正数组元素写入对象;2. 写入完成后用modCount查看与写入之前是否一致
- writeObject
- readObject
- modCount (该属性在ArrayList每次修改的时候都会计数一次,可以用改属性判断最终一致性)例如在每次add方法或者remove等方法调用时都会调用modCount++计数:
小结: 这个时候我们就能发现了,在writeObject方法中处理是为了减少不必要的空元素。因为ArrayList在扩容之后,有的时候实际存放的元素并没有那么多,以1.5倍进行扩容,长度没有满的时候,还会存很多空元素,这些元素也是十分占空间的。
拓展:序列化的时候是如何调用到writeObject和readObject?
事实上集合框架中大部分集合都是用重写writeObject和readObject方法的。有hashMap等等集合
我们发现这两个方法的修饰符都是private,都是私有的那么它们在序列化的时候都是怎么调用到这些方法的呢?
其实不难想出,能调用到私有方法的,通常都是利用反射的。有兴趣的同学可以再去看一下ObjectInputStream方法。在序列化的时候会去反射调用readObject方法的。
总结
最终原因是在扩容的时候,ArrayList是以1.5倍长度扩容的,这样数组会有很多空值元素,会占用大量空间,所以重写的方法由遍历存储数据,就会只序列化不是空值的部分,以节省空间和时间。