史上最详细的Java集合类ArrayList源代码逐行深入解读

(转载请附上链接:https://blog.csdn.net/brucexiajun/article/details/101209837

前言:ArrayList是Java集合类中非常常见的一个类,而且比较基本,不会太难,源代码1500行左右,非常适合新手开始练习源代码的阅读能力。

本文将会尽可能详细的剖析ArrayList类的源代码,文章会陆续更新。

一 注释

我们先从注释开始

ArrayList是对于List接口的大小可变的数组实现,它实现了所有可选的List操作,而且支持所有的数据,包括null。除了实现了List的接口,它还能够操作类的内部用来存储数据的数组大小的方法(这个类和Vector非常像,除了它不是同步的这一点外)

(下面的部分我不再对注释进行截屏,只给出翻译结果)

方法size,isEmpty,get,set,iterator,listIterator运行时间是固定的,add操作运行时间是摊销的,就是说,添加一个元素需要O(n)的时间,所有其他操作的运行时间都是线性的(粗略的说)。相比LinkedList的实现来说,常数因子要小。

每一个ArrayList的对象都有一个容量,指的是用来存储list中的元素的数组的大小,它的大小总是至少为list的大小。随着元素被一个个的添加到ArrayList中,它的容量会自动增长。除了添加一个元素会导致摊销的时间成本外,容量增加的细节没有具体的规定。

在使用ensureCapacity操作向它添加大量元素之前,一个应用可以增加ArrayList对象的容量。这样可能会减少逐渐增加的重新分配过程。(这里翻译感觉有问题)

需要注意的是,这个实现(指ArrayList)不是同步的。如果多个线程同时使用同一个ArrayList的实例,而且其中至少有一个从结构上修改了它,它必须从外部被实现同步机制。(一个结构化的修改指的是一种操作,这种操作会添加或者删除一个或者多个元素,或者显式地修改支持数组的大小,而仅仅设置一个元素的值不是结构化的操作。)典型地,这种同步机制会采用同步一些object来实现,这些object封装了list。

如果这样的object不存在,list需要使用{@link Collections#synchronizedList Collections.synchronizedList}方法来包裹,而且最好是在构建的时候,从而防止可能的对list的非同步操作:

        List list = Collections.synchronizedList(new ArrayList(...));

该类的iterator()和listIterator()方法返回的迭代器是快速失败的:在迭代器被创建出来之后的任何时间,如果list的结构被修改了,除非是通过迭代器自己的方法如ListIterator.remove()或者ListIterator.add(),迭代器会抛出ConcurrentModificationException异常。因此,在同步修改的情况下,迭代器会快速失败,而不是冒着失控的风险:不确定的行为在未来某一个不确定的时间。

注意到迭代器的快速失败行为不能保证如描述的那样,就是说,在不同步的并发修改情况下不可能百分之百保证快速失败。快速失败的迭代器会尽力抛出ConcurrentModificationException异常。因此,编写一个依赖这个异常的的程序是不可取的:迭代器的快速失败行为只能被用来检测bug。

(这个类的注释就这么多,读完之后基本上对类有了认识,下面挑几个方法详细解读一下)

 

二 开始定义的一些变量和常量

我们按照顺序来阅读代码

一开始定义了一些变量,常量:

默认的容量是10

为空的ArrayList准备的空的数组实例

为默认大小的空的ArrayList准备的空的数组实例

elementData就是存放ArrayList元素的地方,这个非常重要,它是一个数组,所以ArrayList内部是使用数组实现的。

transient意思是短暂的,在Java中表示它修改的变量不会被序列化(关于序列化自己去找文章,这里不解释了)。

size表示list对象里面实际存储了几个元素

capacity(容量)表示可以存储几个元素

 

三 构造函数

接下来的内容是构造函数

该类有3个构造函数:

构造一个指定容量的对象,可以很明显的看出,ArrayList本质上就是一个Object类的数组

第二个构造函数简单略过,直接看第三个:

这是用一个Collection的对象构造ArrayList

c.toArray()

返回的是一个Object数组,含有Collection所有的元素,所以大部分情况下到这一句已经结束了。但是c.toArray()有时候会出错,所以有了下面的选择语句:

 elementData = Arrays.copyOf(elementData, size, Object[].class);

这句话的意思是:将elementData转换为长度是size,类型是Object[].class的数组,一般情况下返回的就是本身,除非size比本身长度要长,那么剩下的空间就用null补齐。

下面我会挑一些核心的方法解释。

 

四 indexOf()方法

indexOf的作用是返回对象o在list中第一次出现时的索引。

首先,区分一下o是不是null,因为equals方法不支持null参数,然后从索引0处开始遍历,找到了o就直接返回。最后,如果找不到,返回-1,表示list中不存在这个元素。

 

五 add()方法

核心是第一句,所以我们先来看一下ensureCapacityInternal()方法

看一下里层的calculateCapacity方法:

大部分情况下都是直接返回minCapacity,而这个minCapacity传进来的就是size+1,也就是当前元素的个数加上1.

再来看一下外层的函数ensureExplicitCapacity:

关于这个modCount,在它初始化的地方有一段的注释,我简要翻译一下:

modCount表示list已经被结构化修改的次数,主要用在iterator()和listIterator()方法中。

因为add属于结构化的修改,所以这里modCount会加1

minCapacity是我们需要的长度(也就是添加一个元素之后元素的个数),而elementData.length是当前存储元素的数组的长度,相减大于0表明数组需要扩充了,所以调用了grow()方法:

第二行,newCapacity等于本身加上本身右移一位(乘以2)

第三行,如果newCapacity还是小于minCapacity(实际需要的长度),直接赋值

第五行,如果newCapacity大于一个上限(0x7fffffff-8),则进行一个特殊处理(这里我们不深入了,没必要)

第七行,核心,copyOf就是新建一个数组,并把原数组的元素拷贝进去,新数组的长度是newCapacity。

总结一下add方法,如果需要的长度大于数组本身的长度,就扩充,扩充后的大小一般为原来数组的长度加上它的一半,扩充之后将原来的元素拷贝进去就可以了。

 

六 remove(int index)方法

它的源代码如下所示:

注释的意思是:

删除list中指定位置的元素,然后把后面的元素统统左移

下面逐行来看一下代码:

第一句是检查索引的范围,超出范围就会抛出IndexOutOfBoundException异常

第二句,记录修改次数的参数加1,因为这里我们做了结构化的修改

第三句,取出这个位置的元素,因为用数组存储的,直接取出

第四句,计算需要移动的元素的个数

第五句,判断是否有需要移动的元素,有的话调用System.arraycopy(一个native方法,用c或者c++实现的)来移动元素

第八句,将最后一个元素赋值为空(因为它左移了),提醒垃圾回收机制进行回收

最后返回这个位置的元素

所以其实这个remove也很简单

 

好了,ArrayList的方法就介绍这么多,因为它的方法都比较简单,没有涉及到复杂的数据结构,后面我会出一篇文章介绍ConcurrentHashMap,这个会有意思一点。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值