Java集合-ArrayList深入浅出源码分析

ArrayList定义

ArrayList底层以数组实现,允许重复,默认第一次插入元素时创建数组的大小为10,超出限制时会增加50%的容量,每次扩容都底层采用System.arrayCopy() native方法复制到新的数组,减小到最低开销,初始化时最好能给出数组大小的预估值。

ArrayList类继承关系

如下图为ArrayList的继承图

《Java集合-ArrayList深入浅出源码分析》
红色虚线框表示接口,绿色虚线框表示抽象类。

关键属性详解

1.DEFAULT_CAPACITY:默认初始容量,大小为10个。通过new ArrayList()方式构造时,将分配默认的容量大小。
2.Object[] elementData:存储元素数组。
3.size:数组存储的元素个数。
4.modCount:这个属性继承自AbstractList,记录数组元素size被修改的次数,改变数组size的方法有add,remove,clear等方法,set方法更新值不会改变size大小,因此也不会改变这个值。modCount主要用来检测ArrayList是否有并发修改的操作,在迭代时,若有其他线程并发修改了数组结构,则会抛出ConcurrentModificationException异常。

ArrayList的实现

从上面的属性详解中知道,ArrayList的实现,是在每个实例对象中维护了一个Object数组elementData,为普通类型,非线程安全。而Vector为线程安全,Vector在每个方法上都加了synchronized。如果想使用适用线程安全的方式操作ArrayList,推荐使用Collections.synchronizedList()包装,而非Vector。ArrayList容许存放null元素,这一点容易引起bug,例如,遍历ArrayList元素时若果程序未检查null,则可能抛出NullPointerException异常。

此类的 iterator和 listIterator方法返回的迭代器是“快速失败的”:在创建迭代器之后,除非通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException
。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug。

关键方法源码分析

get方法

get()方法的实现超简单:

《Java集合-ArrayList深入浅出源码分析》
步骤:
1.检查索引下表是否越界,若越界,则抛出IndexOutOfBoundsException异常。
2.返回对应下表的元素

add方法

add()方法实现稍微复杂点,设计到容量的扩容。

《Java集合-ArrayList深入浅出源码分析》

《Java集合-ArrayList深入浅出源码分析》

《Java集合-ArrayList深入浅出源码分析》

步骤:
1.先调用ensureCapacityInternal方法,在这个方法中没干什么实际意义的事情,仅调用了另外一个方法进行是否扩容判断。
2.在ensureExplicitCapacity方法中,扩容时,会按原容量 >> 1的计算公式进行扩容,相当于除以2,即每次扩容50%。
3.扩容后,会将原值拷贝到新的数组中,通过Arrays.copyOf进行复制。
4.扩容后,将元素添加到末尾,并更新size计数器。

size方法

size方法直接返回维护的size变量值。因此在多线程修改数组时,该值是不准确的。所以在调用size方式时,需要小心谨慎,确认没有其他线程修改数组才能正确返回。

contains和indexOf方法

《Java集合-ArrayList深入浅出源码分析》
这两个方法也是平常比较常用的,contains中调用的是indexOf方法。从indexOf方法看,实现也非常简单:
1.如果查找null元素,则遍历数组找到第一个null返回
2.找非null元素,则遍历数组,利用父类AbstractList的equals方法判断是否相同,相同则返回,否则返回-1。

trimToSize

《Java集合-ArrayList深入浅出源码分析》
trimToSize是一个节省空间的操作,考虑这样一种情形,当某个应用需要一个ArrayList扩容到size=10000,之后经过一系列remove操作size=15,在后面的很长一段时间内这个ArrayList的size一直保持在<100以内,那么就造成了很大的空间浪费,这时候建议显式调用一下trimToSize()这个方法,以优化一下内存空间。或者在一个ArrayList中的容量已经固定,但是由于之前每次扩容都扩充50%,所以有一定的空间浪费,可以调用trimToSize()消除这些空间上的浪费。

与LinkedList的区别

1、ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2、对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3、对于新增和删除操作add和remove(不是在尾部添加删除),LinkedList比较占优势,因为ArrayList要移动数据。

    原文作者:java集合源码分析
    原文地址: https://blog.csdn.net/yangkang029/article/details/77414111
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞