本文基于jdk1.8
介绍ArrayList
。
java 集合框架
Java
的集合类主要由两个接口派生而出:Collection
和Map
,在前几篇文章中,我们介绍了map
接口的几个常用实现类。在接下来的文章中,我们将介绍另一个接口Collection
的常用实现类。这些类包括List
接口下的ArrayList
和LinkedList
,Set
接口下的HashSet
和LinkedHashSet
。下面给出Collection
接口的类图:
ArrayList
结合Collection
接口类图和ArrayList
类图我们可以看到,ArrayList
直接继承自AbstractList
,并实现了List
接口。AbstractList
是一个继承于AbstractCollection
并且实现List
接口的抽象类。它实现了List
中大部分的方法,从而方便其它类继承List
。下面我们来看一下它的数据结构。
数据结构
ArrayList
实现了 List
接口,它是基于数组的一个实现,意味着可以插入空值,也可以插入重复的值,并且是非线程安全的。结合源码我们可以看到,在ArrayList
内部维护着一个Object
类型的数组elementData
,这个数组就是存放数据元素的结构,所以ArrayList
所使用的数据结构为数组,就像它的名字一样。但是我们知道,数组的长度是固定的,而ArrayList
的长度却是可变的,这是因为ArrayList
内部是以动态数组的形式来存储数据的,这里的动态数组不是意味着去改变原有内部生成的数组的长度,而是保留原有数组的引用,将其指向新生成的数组对象,这样会造成数组的长度可变的假象。DEFAULT_CAPACITY
表示数组默认的元素个数,超过这个个数就会触发扩容机制,扩容会设置新的存储能力为原来的1.5倍。Size
表示数组elementData
元素的个数。
|
|
由于使用了数组的方式存储数据,ArrayList
具有数组所具有的特性,元素顺序性、元素可重复等,通过索引支持随机访问,所以通过随机访问ArrayList
中的元素效率非常高。
构造方法
ArrayList
提供了以下三个构造方法:
由于使用了数组的实现方式,容量不足时需要扩容,在使用时如果能确定容量大小,最好使用带容量大小的构造方法去使用,可以免去动态扩容带来的性能开销。
存取实现
下面我们结合源码来看一下存取的实现过程。
元素添加 add
首先看一下添加元素的add
方法,add
方法只有寥寥几行,它内部调用了一个ensureCapacityInternal
方法,然后紧跟着就是执行对数组elementData
元素的赋值。通过上面的分析和查看源代码,我们可以知道,这个ensureCapacityInternal
是用来实现数组扩容的功能,扩容结束,又可以继续嗨了,下面我们重点来看一下这个方法。
ensureCapacityInternal
方法及内部方法
|
|
上面三个方法完成了对数组elementData
的容量检查和扩容,保证了数组在保存元素时有足够的容量。整个检查和扩容的流程如下
1、获取数组当前元素数
size
,将size+1
代表数组所需要的长度。
2、校验数组是否为空,为空则需要进行扩容,扩容的长度就是DEFAULT_CAPACITY与minCapacity
的较大值。如果使用默认的无参构造方法,实例化的ArrayList
的数组则为空,如果使用带数组长度的构造方法,实例化的ArrayList
的数组长度则为构造的长度。
3、执行扩容前的计算,决定扩容的长度。这里利用三个变量完成计算,说明一下。oldCapacity
:数组当前的长度,minCapacity
: 存储数据至少需要的长度 ,newCapacity
:数组新的长度
首先保存当前数组的长度,然后将数组大小扩展1.5倍赋值给数组新的长度(newCapacity = oldCapacity + (oldCapacity >> 1)
),这里使用了位运算符,右移一位相当于除2,右移n位相当于除以2的n次方。如果新的长度小于所需容量,将所需容量赋值给新的数组长度,这种情况会在数组为空的时候发生。然后如果新的数组长度大于数组的最大长度MAX_ARRAY_SIZE
,就重新计算数组长度,最大的数组长度可以为Integer.MAX_VALUE
。
4、扩容长度计算完毕,执行扩容,将数组内容拷贝到新的数组,并将新的数组赋值给elementData
,完成扩容。
5、扩容完成,执行添加元素操作。
从这个过程可以看出,如果频繁的对ArrayList
进行插入操作而同时又不指定集合的长度的话,就会频繁的引起数组动态扩容,由于扩容是使用的数组的拷贝方法,这种方式不仅牺牲了性能而且也对内存空间造成了浪费,如果数据量比较大的话可能会带来比较严重的性能问题,所以如果我们提前能清楚的知道所处理数据的大小,就可以构造一个固定长度的ArrayList
,也就省去了动态扩容的开销。
遍历和查找
说完了添加,下面再说说查找。数组的优势就在于查找了,数组可以使用索引访问任意的元素,实现高效的查找和遍历。查找就是根据下边找到数组元素,比较简单,这里就不再说了,下面来看一下遍历。对ArrayList
遍历有以下几种方法:Iterator
方式,for
循环索引随机访问方式,foreach
方式,对于这几种方式来说,使用随机访问的方式遍历是效率最高的。因为ArrayList
实现了RandomAccess
接口,此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。
总结
基于以上的分析,我们来总结以下ArrayList
的特性和注意点。
1、ArrayList
是基于数组的数据结构实现,拥有数组的一般特性,有序性,元素可重复、可为空以及高效的随机访问能力,比较适用于频繁读取数据的场景。
2、由于是动态数组,容量不够时需要扩容,扩容带来性能问题,不适合用于频繁增删的场景,如果可以确定数据量大小,推荐使用固定的容量实例化。
3、非线程安全,多线程环境谨慎使用。
参考
http://blog.csdn.net/yang1464657625/article/details/59109133
http://www.cnblogs.com/leesf456/p/5308358.html