(转载请附上链接: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,这个会有意思一点。