JDK 1.8 java集合源码分析一(ArrayList)

工作有几年了,说来惭愧,从来没认真仔细的分析过JDK里面的源码,从今天开始分析下JDK中集合部分源码,学习下大神的思路,如有错误,大家尽管指出。

JDK版本 JDK_1.8.0_201

编辑器:idea 2019.3

首先我们看一下Collection接口

看一下Collection接口的各种关系,idea中 ctrl + h

JDK 1.8 java集合源码分析一(ArrayList)_第1张图片

我们只看其中的Set与List

用一个UML图画一下

JDK 1.8 java集合源码分析一(ArrayList)_第2张图片

这里面有我们最常用到的ArrayList与LinkedList,以及HashSet 与LinkedHashSet

先看ArrayList类

JDK 1.8 java集合源码分析一(ArrayList)_第3张图片

实现了四个接口、一个抽象类

JDK 1.8 java集合源码分析一(ArrayList)_第4张图片

四个不可修改的变量

serialVersionUID:序列化需要的值
DEFAULT_CAPACITY :默认初始容量
EMPTY_ELEMENTDATA:用于空实例的共享空数组实例
DEFAULTCAPACITY_EMPTY_ELEMENTDATA:也是一个空的数组
elementData:真正存放元素的数组,用transient 关键字修饰?
size:元素个数

问题:为啥会有两个空的数组?transient 关键字?后续说明吧。

先看构造方法

JDK 1.8 java集合源码分析一(ArrayList)_第5张图片

1.指定大小的初始化方法,如果初始化数量等于0,elementData初始化为EMPTY_ELEMENTDATA数组,如果初始化入参小于0则抛出异常

2.不指定大小,默认空数组,elementData初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA

3.将集合转为ArrayList

JDK 1.8 java集合源码分析一(ArrayList)_第6张图片

4.trimToSize方法

首先说明下modCount的作用,唉,看个源码有这么多东西需要学习。。。。。

看下官方解释,来自AbstractList中

JDK 1.8 java集合源码分析一(ArrayList)_第7张图片

噼里啪啦一大堆,看滴脑阔痛。

原文大概意思:

这个字段表示list被结构性更改的次数,结构性更改是指改变了list的长度大小或者以别的方式干扰了这个值,会导致在迭代的过程中产生不正确的结果。

此字段由迭代器和列表迭代器实现使用,在next、add、remove、previous、set操作中如果此字段的值意外更改,则迭代器(或列表迭代器)将在其中抛出{ConcurrentModificationException}异常(阿里巴巴java开发手册关于list的遍历删除的规范有说明)面对迭代期间的并发修改,这提供了快速失败的行为,而不是不确定的行为

子类对于此字段的使用是可选择的,如果子类希望提供快速失败的迭代器(和列表迭代器)只需要在add、remove、等有可能结构性更改list的方法中增加这个字段的值。单线程调用add、remove方法不能删除正在遍历的对象。否则将可能抛出ConcurrentModificationException异常,如果子类不希望支持快速失败,该字段可以直接忽略。

阿里java开发手册

言归正传,这个方法平时基本上不会用到,第一次看到这个

JDK 1.8 java集合源码分析一(ArrayList)_第8张图片

可以在debug的过程中查看这个变化过程

JDK 1.8 java集合源码分析一(ArrayList)_第9张图片

初始化一个大小为10 的list,在list中添加了十个元素,这个时候可以看到elementData的数组大小是10,然后继续向下走,添加一个新元素,这个时候发现elementData的数组大小变为了15,这是它自动扩容了,这个时候的size的大小就不等于数组的大小了。继续走,把trimtosize方法走完,发现size的大小变的跟数组elementData的大小一样了。这就是这个方法的作用。

JDK 1.8 java集合源码分析一(ArrayList)_第10张图片

既然讲到这了,先看一下它的add方法吧

JDK 1.8 java集合源码分析一(ArrayList)_第11张图片

整体看起来比较简单,就是第一行调用了一个方法,我们来看看这个方法的内容,从字面的意思看是“确保内部容量”的意思。

先看一下这个calculateCapacity计算容量的方法,在add之后会传size+1的值过来做计算

JDK 1.8 java集合源码分析一(ArrayList)_第12张图片

再看ensureExplicitCapacity方法,字面意思:“确保准确的容量”

JDK 1.8 java集合源码分析一(ArrayList)_第13张图片

扩容方法

JDK 1.8 java集合源码分析一(ArrayList)_第14张图片

数组最大值,为啥是int最大值-8?

  

16进制数据,转成10进制为2147483647,也就是2的31次方-1的值,也就是int的最大值

看看hugeCapacity方法对所需的最小容量值干了什么吧

JDK 1.8 java集合源码分析一(ArrayList)_第15张图片

所以最大容量理论上可以达到Integer.MAX_VALUE的值,这已经是一个很大的数字了。

所以add一个元素的时候,如果实际元素数量大于数组的真实长度,那么就会执行扩容方法。

既然add讲完了,那么顺手看看get方法

JDK 1.8 java集合源码分析一(ArrayList)_第16张图片

这个其实很简单啊,就是返回指定位置的数组的值就行。当然在返回值之前有一个判断方法rangeCheck

JDK 1.8 java集合源码分析一(ArrayList)_第17张图片

也就是如果指定的数组位置大于总大小,会抛出越界的异常

JDK 1.8 java集合源码分析一(ArrayList)_第18张图片JDK 1.8 java集合源码分析一(ArrayList)_第19张图片

这个indexOf方法也很简单,返回指定元素在ArrayList中第一次出现的位置,同理,lastIndexOf返回最后一次出现的位置。

JDK 1.8 java集合源码分析一(ArrayList)_第20张图片

返回一个副本的ArrayList集合

JDK 1.8 java集合源码分析一(ArrayList)_第21张图片

上面这个也比较简单,给指定位置的ArrayList设置值

JDK 1.8 java集合源码分析一(ArrayList)_第22张图片

在判断是否需要扩容的同时将数组的length+1

arraycopy方法,顾名思义,数组复制方法。做一个简单的演示

System.arraycopy(elementData, index, elementData, index + 1, size - index);

假如elementData现在存放的数据是{1,2,3,4,5},由于在判断是否扩容方法中重新构建过数组,这时的length是6,index = 3,由于那么执行这个方法的入参就是System.arraycopy(elementData, 3, elementData, 4, 2);最后得到结果是{1,2,3,4,4,5}。就是将数组的3位开始复制2位数据,从数组的第四位开始写进去,也就是{1,2,3,4}+{4,5}。

下面是删除方法,删除第几个元素

JDK 1.8 java集合源码分析一(ArrayList)_第23张图片

下面看删除指定元素方法

JDK 1.8 java集合源码分析一(ArrayList)_第24张图片

阅读起来应该没有压力,看fastRemove方法

JDK 1.8 java集合源码分析一(ArrayList)_第25张图片

与remove(int index) 方法大同小异,但是没有越界的校验,并且也没有返回要删除的值。(前面用到这个方法的删除方法,元素肯定在数组中,也就无需校验)

数组清空方法

JDK 1.8 java集合源码分析一(ArrayList)_第26张图片

将数组中是所有元素置为NULL,并且size置为0

下面看addAll方法,也比较简单

JDK 1.8 java集合源码分析一(ArrayList)_第27张图片

从指定位置开始addAll,与addAll方法大同小异,不再详细说明,就贴个代码吧

JDK 1.8 java集合源码分析一(ArrayList)_第28张图片

范围删除,从第几个元素开始删到第几个元素,也比较容易

JDK 1.8 java集合源码分析一(ArrayList)_第29张图片

两个校验方法,这两个异常初学者应该见的比较多

JDK 1.8 java集合源码分析一(ArrayList)_第30张图片

删除集合方法

JDK 1.8 java集合源码分析一(ArrayList)_第31张图片

JDK 1.8 java集合源码分析一(ArrayList)_第32张图片

这个方法里面设计的很巧妙,r!=size那块我觉得还是单独拿出来说明。正常流程r=size的流程应该还比较好懂

注释里面说了,如果c.contains()抛出异常,会导致r!=size

我们来做个实际例子,elementData={2,2,3,4,5,6} Collection c = {2,5,9},size = 6 

假如现在在判断c.contains(4)的时候报错,那么现在elementData={3,2,3,4,5,6} ,w = 1, r = 3,size=6

那么现在进入到r!=size 的if中,System.arraycopy(elementData, 3, elementData, 1, 3)的结果就是{3,4,5,6,5,6},w= w+ size -r = 4

然后继续执行w!=size的中的if内容 w = 4 从4开始的数据置为nul,修改size,修改modeifed = true。

总的来说这个方法看起来还是有点麻烦,但是用起来却没啥困难。写这个代码的人实在是大牛中的大牛。

JDK 1.8 java集合源码分析一(ArrayList)_第33张图片

retainAll方法也是用的batchRemove方法来实现的,这里就不再讲一遍了

 

还有常用的截取的方法

public List subList(int fromIndex, int toIndex)

JDK 1.8 java集合源码分析一(ArrayList)_第34张图片

这个是一个截取的方法,但是要注意下,这个截取后返回的是sublist类,并不是Arraylist类型。

阿里的手册里面有写道

JDK 1.8 java集合源码分析一(ArrayList)_第35张图片

最后看一看这个forEach循环遍历

JDK 1.8 java集合源码分析一(ArrayList)_第36张图片

最终实现其实还是普通的for循环,只是要注意在使用的过程中不要对原list进行结构性改变,否则有可能会抛出异常。

最常用的这些方法我大致都列出了,欢迎大家来补充,有问题直接指出就行。互相学习!

 

你可能感兴趣的:(java,java)