java jdk1.7版本的ArrayList原理解析

来源:互联网 发布:五十知天命六十是什么 编辑:程序博客网 时间:2024/06/03 11:42

上一篇文章中我们了解了LinkedList集合的底层实现原理,这一篇将研究下ArrayList的实现原理。
同样的通过源码解释,我们可以得到以下信息:
(1)、ArrayList是一个长度可变的数组集合,创建ArrayList实例时没有指定长度,当添加数据,而且数据个数小于10时,那么默认将会创建一个容量为10的数组存储数据;当数组不够长时,将会默认增长原来容量的一半。如果要自定义ArrayList的容量,可以通过调用ensureCapacity(int size)方法指定容量,也可以在创建ArrayList时指定容量,ArrayList最大的容量不能超出Integer.MAX_VALUE(2147483647)否则会抛出OutOfMemoryError异常。
(2)、不同的方法访问ArrayList的时间复杂度不一样。size, isEmpty, get, set,iterator,listIterator的时间复杂度是固定的,add、remove方法是不固定的,因为有可能需要移动元素。
(3)、ArrayList不是线程安全的,所以在多线程的环境下使用ArrayList需要注意ArrayList类型变量的线程同步问题。当然,有一种方式可以创建一个线程安全的ArrayList:
List list = Collections.synchronizedList(new ArrayList(…));
(4)、ArrayList的迭代器 iterator 和 listIterator 方法返回的迭代器是快速失败 的。所谓快速失败,意思就是如果在迭代器已经创建了的情况下,任何时刻对ArrayList结构的修改,迭代器将会抛出一个ConcurrentModificationException异常。如下面代码中迭代器已经生成,但是还往ArrayList添加数据,那么此后的迭代过程是会抛出ConcurrentModificationException异常的:
这里写图片描述

ArrayList底层是基于数组来保存数据的,这个数组就保存着ArrayList的真实数据。内部定义了这个数组:
这里写图片描述
没错!elementData就是ArrayList真正保存数据的数组。

接下来我们先了解下ArrayList的容量增长策略,先上源码:
这里写图片描述

这里写图片描述
对ArrayList进行自增长的入口是ensureCapacityInternal(int minCapacity)方法,这里说明一下实现自增长过程中有几个会使用到的常量:
(1)、elementData:这个变量在前面已经说过,是存放ArrayList真实数据的数组;
(2)、EMPTY_ELEMENTDATA:这是一个容量为0的空数组,这个常量是不会存储真实数据的,即它永远都是空的。 源码是这样定义的:
这里写图片描述
(3)、DEFAULT_CAPACITY:ArrayList的默认容量,如果ArrayList没有指定容量,并且需要保存数据,但是数据的真实个数不够DEFAULT_CAPACITY大小的情况下,将会创建一个DEFAULT_CAPACITY大小的数组(即elementData)来存放添加的数据;
(4)、MAX_ARRAY_SIZE:Integer的最大值,但是源码中实际的长度会比Integer的最大值小8,因为java中是这样定义的:
这里写图片描述

对照这些常量就很好理解上面ArrayList容量实现自增长的原理:这里总结下:
(1)、如果当前ArrayList的数据为空,并且添加的数据个数小于10,那么java将会分配一个长度为10的数组来存放数据;
(2)、如果当前ArrayList的数据不为空,并且数组的大小可以容纳新增的数据,那么将不进行自增长操作;
(3)、如果当前ArrayList的数据不为空,并且数组的大小不可以容纳新增的数据,那么将进行自增长操作,并且是这样增长:
如果(size+size/2)>size+新增数据的个数,那么将ArrayList的容量增长到(size+size/2)大小;
如果size+size/2)小于size+新增数据的个数,那么将ArrayList的容量增长到size+新增数据的个数大小;
如果size+新增数据的个数>MAX_ARRAY_SIZE,那么将ArrayList的容量增长到Integer.MAX_VALUE大小,否则将ArrayList的容量增长到MAX_ARRAY_SIZE;
如果size+新增数据的个数>Integer.MAX_VALUE,则抛出OutOfMemoryError异常。

这里需要注意一下,当ArrayList容量扩充完成之后会调用这样一句代码:
elementData = Arrays.copyOf(elementData, newCapacity); 这句代码的意思就是按照新的容量大小来创建一个新的数据,并且把原来数据的数据拷贝过来。不懂 Arrays.copyOf()看看便明白。

知道了ArrayList是基于数组来实现的,下面将看下具体的实现:
1、add(E e):该方法添加一个数据到ArrayList链表最后的位置,jdk中的源码:
这里写图片描述
新增一个数据的源码非常简单,如果不看ensureCapacityInternal(size + 1);这一句代码,实际上就是往数组的size索引这个位置放入一个数据。size是ArrayList的大小。注意,这里的size不是数组的大小,而是数组里真正存在数据的大小,也即是ArrayList的大小。比如ArrayList的size是10,但是数据的长度有可能是100,只有前10个位置存放了真正的数据。这里需要注意!!!

2、add(int index, E element):在指定的index位置添加一个数据,index之后的数据将依次往后移:
这里写图片描述
同样的,这里也是直接在数据的index位置放入一个数据,但是在这之前,判断了传入的index是否有越界;同时也判断了ArrayList是否需要扩充容量,并且通过数据拷贝的方式把index后的数据往后移。其实数据的移动都是通过调用System.arraycopy()进行移动的。

3、remove(int index):remove方法移除元素之后,index后面的数据是往前移动,同样也是通过System.arraycopy()方法实现:
这里写图片描述

4、lastIndexOf(Object o):lastIndexOf直接遍历数组,但是是从后往前遍历,因为是寻找最后一个要查找的对象:
这里写图片描述

ArrayList底层实现原理是通过数组实现,因此其优点是随机访问效率比较高,但是随机插入和删除元素比较慢,因为要对其它元素进行移动。

0 0
原创粉丝点击