java集合类源码详解-ArrayList(1)-基于JDK8

       最近在疯狂的补基础 在java中 最重要的知识之一 非集合类莫属。这次在学习java集合类源码,采用的是传统的方法,断点调试和写测试代码。由于是刚开始接触java集合类源码。所以一开始只写了两句代码来测试,毕竟源码学习是很缓慢的过程。只能慢慢的啃。在阅读源码前,我是把ArrayList和AbstractArrayList都拷贝出来了(不拷贝出来是不能对源码修改的),而且在很多方法前都加了注释方便阅读。阅读源码的环境采用IDEA。

         下面是加了注释的ArrayList源码地址:https://pan.baidu.com/s/16fXEjoKQwfbmcqB0fUekXw 密码:m5se

接下来就以这两句代码来追踪调试查看 执行过程。一步步的学习源码。

          

打上两个端点,点击dbug。这里有一个警告,是因为往testArrayList里面添加了元素但却没有做任何操作。

IDEA怎么调试相比大家都会,打上断点后,点击右上的一只绿色的虫的行了。

这里我们一步步的点击下一步。因为ArrayList有很多静态变量,所以当第一次点击下一步的时候,会跳转到源码静态变量这里来。

接着再点击下一步。由于我们测试语句的第一句是 new ArrayList()。我们在new的时候没有指定容器大小。所以底层肯定会执行一个无参构造。很显然,我们的猜想是正确的。

这里要注意,当我们再点击下一步的时候,会去执行AbstractList,因为ArrayList继承自抽象类AbstractLIst,在执行自己的构造函数时会先调用父类的无参构造。

此时我们再点下一步,会发现一个奇怪的现象,底层执行并没有回到Arraylist的无参构造里面继续执行。而是去初始化一个变量modCount。这个modCount是什么?前几天我查了资料了解了一下。

该变量主要用来实现
* fail-fast机制的(fail-fast 机制是java集合(Collection)中的一种错误机制。
* 当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件)

接着再点击下一步。回到了AbstractList的构造函数,执行结束

然后再次点击下一步。这个时候回到了ArrayList的无参构造,这里将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋给elementData。

elementData: ArrayList基于数组实现,用该数组保存数据, ArrayList 的容量就是该数组的长度
DEFAULTCAPACITY_EMPTY_ELEMENTDATA: 一个Object类型的空数组实例
* - 当用户没有指定 ArrayList 的容量时(即调用无参构造函数),
* 返回的是该数组

再次点击下一步,此时我们测试代码的第一句就已经结束了,接着开始执行add方法。

点击下一步。此时会在add方法内部执行一个函数esureCapacityInternal 。

size:ArrayList实际存储的数据数量,一开始没做任何操作,默认初始化为0

再次点击下一步,这里会判断elementData是否等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,即判断是否用户在创建容器的时候没有指定容量。这里肯定是True。此时会执行一个Math.max方法。这个方法的作用是:将DEFAULT_CAPACITY和mincapacity比较谁大。这里DEFAULT_CAPACITY是10,而mincapacity是1,所以这里会把10赋值给mincapacity。

DEFAULT_CAPACITY: 默认容量,默认大小是10

这里再点击下一步,这里会执行一个函数,参数为minCapacity

再点击下一步。这里modCount自增。楼主,查过一个帖子,上面说凡是出现这个modCount操作的集合类。这个集合类都是线程不完全的,但是是否是这样还需要之后去求证。

再次点击下一步。将minCapacity与element.length做比较,此处的minCapacity大小为10。element.length是0

所以显然是true。

点击下一步。这里将elementData.length赋给oldCapacity。然后将自身加上自身做右移运算。

如果新的容量还是比minCapacity小,那就把最小容量赋给它。

点击下一步。这里对newCapacity进行判断。是否比MAX_ARRAY_SIZE大。如果比其大还得执行hugeCapacity。这个函数里面其实就是个判断和三元运算,可以自己去看。这里我们的测试语句是不会进hugeCapacity的。

MAX_ARRAY_SIZE :
数组缓冲区最大存储容量
* - 一些 VM 会在一个数组中存储某些数据--->为什么要减去 8 的原因
*  - 尝试分配这个最大存储容量,可能会导致 OutOfMemoryError(当该值 > VM 的限制时)
*   Integer.MAX_VALUE 值为正21亿

再次点击下一步。接下来进行数据扩容,将newCapacity这个int类型的,值为10。让数组eleementData扩容至这个newCapacity大小。

接下来扩容就完成,开始弹栈,一步步的返回最开始的add函数。执行最终的把值添加到容器中。

这里就是把 1 添加到 elementData[0]。然后把size+1。size为容器的实际大小,通俗点就是里面实际装了多少东西。

注意这里要区分容器的实际大小和容器的容量,当我们实际大小远小于容器的容量,是会造成空间浪费的。

最后返回True。

好了。到这里,这就是两句测试语句的执行过程和底层源码剖析。总结一下。

这两句的底层执行过程就是:

加载静态变量---->public ArrayList()---->protected AbstractList()丶int modCount = 0---->
public boolean add(E e)---->private void ensureCapacityInternal(int minCapacity)
---->private void ensureExplicitCapacity(int minCapacity)---->
---->private void grow(int minCapacity)

后续楼主会继续学习集合类的源码,另外博客什么地方写错,请指出,大家一起学习进步!! 

猜你喜欢

转载自blog.csdn.net/qq_37889257/article/details/84029195
今日推荐