天天看点

ArrayList初见

ArrayList是java集合框架中比较常见的一员,也是了解java框架的一个比较好的切入点。基于java1.8,ArrayList的继承类图如下:

ArrayList初见

ArrayList是java基于数组实现的非线程安全的链表数据结构,允许元素为null。内部维护了一个数组来存储数据并在上面进行操作。下面分别描述一下ArrayList的构造和常用的方法。

成员变量

ArrayList初见
ArrayList初见

上图是ArrayList中的全局变量和常量,DEFAULT_CAPACITY是内部数组默认初次加载的长度,EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是两个静态常量,实例化ArrayList对象的时候会根据不同的情况可能将elementData(即内部数组)指向他们两个其中的一个(如果初始元素个数为0)。elementData就是内部数组,数据都存储在这里。size是链表中元素的个数,MAX_ARRAY_SIZE是链表能够存储的最多的元素的个数。

构造函数

ArrayList初见

如上图所示,ArrayList的构造函数有三个。他们的参数分别为没有、初始数组长度、集合类。接下来分别分析一下。

没有参数的构造函数很简单,直接将内部数组elementData指向了DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

有一个int类型参数的构造函数也比较简单,如果参数为0,则把elementData指向EMPTY_ELEMENTDATA。如果参数大于0,则创建一个大小为参数的对象数组并让elementData指向它。否则抛出异常。

以一个集合类型作为参数的构造函数也比较简单。首先直接将toArray方法的返回值赋值给elementData,然后根据数组的长度是否为零来决定是否把elementData重新指向EMPTY_ELEMENTDATA。此外,如果数组长度大于0,还要判断一下数组的类型是否是对象数组,必要时会通过复制的方式进行转换。如上图中所示。

以上就是ArrayList的全局变量和构造函数。也许你会问EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别。目前看来,他们的确是没有区别的。当执行某些方法的时候,他们的区别才会显现出来。

CRUD

作为链表的实现,我们先来看一下链表的CRUD方法。

ArrayList的add方法有两个,分别是add(E)和add(index, E)。

add(E)

先上add(E)的图

ArrayList初见

第一行暂且不考虑,可以看到代码很简单。就是在数组尾部添加一个元素,然后返回true。

但是在构造函数那里可以看到,ArrayList对象的elementData变量很有可能指向静态数组。那么在这里会将元素添加到静态数组当中去吗?显然不会。奥秘就是第一行代码调用的方法中,它会为数组扩容。上图。

ArrayList初见
ArrayList初见
ArrayList初见

大致说一下它的具体流程,

首先判断内部数组指向的是不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA。如果是的话,取参数与默认值(10)的最大值继续向下传递,否则传递原参数。

第二个方法中判断参数是否大于数组的容量。如果是的话,执行grow方法,否则什么都不执行。

grow方法比较复杂,先获取扩容后的数组的长度和参数中的最大值。如果这个最大值大于常量MAX_ARRAY_SIZE,会执行hugeCapacity方法。在这个方法中根据参数和MAX_ARRAY_SIZE的大小比较返回不同的值并赋值给newCapacity,也就是最后一行代码中用到的值。这里考虑的是int类型边界的问题,可以暂时不考虑。只需要看最后一行代码即可。Arrays.copyOf方法常用于扩容数组,在这里它将复制原数组到一个长度为newCapacity的新数组中并返回给elementData。

回到add(E)方法,可以看到除了数组长度方面的代码(扩容、防止越界),添加元素的部分代码很少,并且在正常情况下恒定返回true。

add(index,E)

上图。

ArrayList初见
ArrayList初见

注释中写的比较明白了,在特定的位置插入特定的元素,并将该位置及其右边的元素整体向右移动。

第一行代码调用的方法如图所示,只是判断index参数不能越界。

第二行代码调用的方法和add(E)方法中一致,不再赘述了。

然后就执行了System.arraycopy方法(Arrays.copyOf方法内部也调用了这个方法,native方法)将当前位置及其右边的元素整体向右移动。

然后再把参数元素放到当前位置。

最后自增一下size变量。

注意这个方法没有返回值。

get(index)

先上图

ArrayList初见
ArrayList初见

方法很简单,先判断一下参数的范围,然后返回参数指定位置的元素。这也是ArrayList的优势,查询速度很多。

set(index,E)

上图

ArrayList初见

正如注释所言,用特定的元素(参数)取代链表特定位置的元素。并返回被取代的元素。代码非常直白,不多说了。

remove(index)

上图

ArrayList初见

没有什么比注释说的更清楚的了:删除链表中特定位置的元素并将其右边所有的元素左移。

第一行依然是检查index的范围。

然后自增modCount变量。

取出指定位置的元素,并将此元素右边所有的元素左移一个元素的位置。

将原数组最右边的元素废弃并将size变量自减。(因为涉及到ArrayList元素的移动都是通过复制来实现的,所以这里需要处理一下以达到整体移动的效果)

返回取出的指定位置的元素。

remove(object)

上图

ArrayList初见
ArrayList初见

如注释所言,删除链表中第一个出现的目标元素,如果不存在这样的元素,那么链表将不会发生变化。如果删除成功将返回true,否则返回false。

看一下方法体内的代码,如果参数为null,那么将遍历链表寻找null,找到之后执行fastRemove(index)方法。否则依然遍历链表寻找特定元素,找到之后执行fastRemove(index)方法。注意这里用的是equals。逻辑并不复杂,简单来讲就是找到目标的位置,并根据位置进行删除。

再来看fastRemove(index)方法,和remove(index)方法差不多。不多说了。

总结

以上简单介绍了ArrayList类的一些基础内容。包括成员变量、构造函数以及对单个元素的操作。实际上ArrayList中的方法远不止于此,很多都值得拿出来单独分析一番。为了篇幅不至于过长,就先到此为止吧。