javaArrayList 源码解析.docx
《javaArrayList 源码解析.docx》由会员分享,可在线阅读,更多相关《javaArrayList 源码解析.docx(13页珍藏版)》请在冰豆网上搜索。
javaArrayList源码解析
ArrayList源码解析
——动力节点java
ArrayList是list接口下一个底层用数组实现的典型list类,也就是传说中的动态数组,用MSDN的说法就是array的复杂版本,它提供了动态的增加和减少元素,实现了ICollection和IList接口,灵活的设置数组的大小等好处。
相对于LinkedList来说,ArrayList的特点是查询数据快,增删数据慢。
1、ArrayList的属性及常用方法。
A、两个私有属性
privatetransientObject[]elementData;//表示存储在ArrayList内的元素
privateintsize;//表示它包含元素的数量
B、三种构造器
//带容量大小的构造方法
publicArrayList(intinitialCapacity){
super();
if(initialCapacity<0)
thrownewIllegalArgumentException("IllegalCapacity:
"+initialCapacity);
this.elementData=newObject[initialCapacity];
}
//无参构造方法,默认大小为10
publicArrayList(){
super();
this.elementData=EMPTY_ELEMENTDATA;
}
publicArrayList(Collection
extendsE>c){
//调用toArray()把Collection转换成数组
elementData=c.toArray();
//把数组的长度赋值给ArrayList的size属性
size=elementData.length;
//c.toArraymight(incorrectly)notreturnObject[](see6260652)
if(elementData.getClass()!
=Object[].class)
elementData=Arrays.copyOf(elementData,size,Object[].class);
}
第一个构造方法使用提供的initialCapacity来初始化elementDate数组的大小,第二个构造方法调用构造方法并传入参数10,也就是默认elementData数组的大小为10.第三个构造方法将提供的集合转成数组返回给elementData(返回若不是Object[]就调用Aeeays.copyOf()将其转为Object[])。
C、ArrayList的动态扩容:
oldcapa*1.5
privatestaticfinalintDEFAULT_CAPACITY=10;
以add方法为入口
publicbooleanadd(Ee){
ensureCapacityInternal(size+1); //IncrementsmodCount!
!
elementData[size++]=e;
returntrue;
}
可见,在添加元素之前,会先调用ensureCapacityInternal这个方法,那就再进到这个这个方法中去。
privatevoidensureCapacityInternal(intminCapacity){
if(elementData==EMPTY_ELEMENTDATA){
minCapacity=Math.max(DEFAULT_CAPACITY,minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
首先,看看数组是否为空,如果是,就将DEFAULT_CAPACITY和minCapacity的较大的一个作为初始大小赋值给minCapacity ,DEFAULT_CAPACITY是10,minCapacity就是add方法中传入的size+1。
如果数组不为空,就直接执行ensureExplicitCapacity方法。
ensureExplicitCapacity方法的实现如下:
privatevoidensureExplicitCapacity(intminCapacity){
modCount++;
//overflow-consciouscode
if(minCapacity-elementData.length>0)
grow(minCapacity);
}
在这个方法里,会比较minCapacity与elementData.length的大小。
当第一次插入值时,由于minCapacity一定大于等于10,而elementData.length是0,所以会去继续执行grow方法,那就继续进入到这个方法中去。
grow方法的实现如下:
Privatevoidgrow(intminCapacity){
//overflow-consciouscode
intoldCapacity=elementData.length;
intnewCapacity=oldCapacity+(oldCapacity>>1);
if(newCapacity-minCapacity<0)
newCapacity=minCapacity;
if(newCapacity-MAX_ARRAY_SIZE>0)
newCapacity=hugeCapacity(minCapacity);
//minCapacityisusuallyclosetosize,sothisisawin:
elementData=Arrays.copyOf(elementData,newCapacity);
}
这个方法首先计算出一个容量,大小为oldCapacity+(oldCapacity>>1)。
即elementData数组长度的1.5倍。
再从minCapacity和
这个容量中取较大的值作为扩容后的新的数组的大小。
这时,会出现两种情况:
1)新的容量小于数组的最大值MAX_ARRAY_SIZE,即
privatestaticfinalintMAX_ARRAY_SIZE=Integer.MAX_VALUE-8;
最大的容量之所以设为Integer.MAX_VALUE-8,在定义上方的注释中已经说了,大概是一些JVM实现时,会在数组的前面放一些额外的数据,再加上数组中的数据大小,有可能超过一次能申请的整块内存的大小上限,所以会出现OutOfMemoryError。
2)新的容量大于数组的最大值MAX_ARRAY_SIZE
这时,又会进入另一个方法hugeCapacity()
privatestaticinthugeCapacity(intminCapacity){
if(minCapacity<0)//overflow
thrownewOutOfMemoryError();
return(minCapacity>MAX_ARRAY_SIZE)?
Integer.MAX_VALUE:
MAX_ARRAY_SIZE;
}
会对minCapacity和MAX_ARRAY_SIZE进行比较,minCapacity大的话,就将Integer.MAX_VALUE作为新数组的大小,否则将MAX_ARRAY_SIZE作为数组的大小。
最后,就把原来数组的数据复制到新的数组中。
调用了Arrays的copyOf方法。
内部是System的arraycopy方法,由于是native方法,所以效率较高。
二、ArrayList的其他方法
add(Ee)
add(Ee)都知道是在尾部添加一个元素,如何实现的呢?
1.public boolean add(E e) {
2. ensureCapacity(size + 1); // Increments modCount!
!
3. elementData[size++] = e;
4. return true;
5. }
1.public boolean add(E e) {
2. ensureCapacity(size + 1); // Increments modCount!
!
3. elementData[size++] = e;
4. return true;
5. }
都说ArrayList是基于数组实现的,属性中也看到了数组,具体是怎么实现的呢?
比如就这个添加元素的方法,如果数组大,则在将某个位置的值设置为指定元素即可,如果数组容量不够了呢?
看到add(Ee)中先调用了ensureCapacity(size+1)方法,之后将元素的索引赋给elementData[size],而后size自增。
例如初次添加时,size为0,add将elementData[0]赋值为e,然后size设置为1(类似执行以下两条语句elementData[0]=e;size=1)。
将元素的索引赋给elementData[size]不是会出现数组越界的情况吗?
这里关键就在ensureCapacity(size+1)中了。
根据ensureCapacity的方法名可以知道是确保容量用的。
ensureCapacity(size+1)后面的注释可以明白是增加modCount的值(加了俩感叹号,应该蛮重要的,来看看)。
1./**
2. * Increases the capacity of this ArrayList instance, if
3. * necessary, to ensure that it can hold at least the number of elements
4. * specified by the minimum capacity argument.
5. *
6. * @param minCapacity the desired minimum capacity
7. */
8. public void ensureCapacity(int minCapacity) {
9. modCount++;
10. int oldCapacity = elementData.length;
11. if (minCapacity > oldCapacity) {
12. Object oldData[] = elementData;
13. int newCapacity = (oldCapacity * 3)/2 + 1;
14. if (newCapacity < minCapacity)
15. newCapacity = minCapacity;
16. // minCapacity is usually close to size, so this is a win:
17. elementData = Arrays.copyOf(elementData, newCapacity);
18. }
19. }
这是对modCount的解释,意为记录list结构被改变的次数(观察源码可以发现每次调用ensureCapacoty方法,modCount的值都将增加,但未必数组结构会改变,所以感觉对modCount的解释不是很到位)。
增加modCount之后,判断minCapacity(即size+1)是否大于oldCapacity(即elementData.length),若大于,则调整容量为max((oldCapacity*3)/2+1,minCapacity),调整elementData容量为新的容量,即将返回一个内容为原数组元素,大小为新容量的数组赋给elementData;否则不做操作。
所以调用ensureCapacity至少将elementData的容量增加的1,所以elementData[size]不会出现越界的情况。
容量的拓展将导致数组元素的复制,多次拓展容量将执行多次整个数组内容的复制。
若提前能大致判断list的长度,调用ensureCapacity调整容量,将有效的提高运行速度。
可以理解提前分配好空间可以提高运行速度,但是测试发现提高的并不是很大,而且若list原本数据量就不会很大效果将更不明显。
add(intindex,Eelement)在指定位置插入元素。
1.public void add(int index, E element) {
2. if (index > size || index < 0)
3. throw new IndexOutOfBoundsException(
4. "Index:
"+index+", Size:
"+size);
5.
6. ensureCapacity(size+1); // Increments modCount!
!
7. System.arraycopy(elementData, index, elementData, index + 1,
8. size - index);
9. elementData[index] = element;
10. size++;
11. }
首先判断指定位置index是否超出elementData的界限,之后调用ensureCapacity调整容量(若容量足够则不会拓展),调用System.arraycopy将elementData从index开始的size-index个元素复制到index+1至size+1的位置(即index开始的元素都向后移动一个位置),然后将index位置的值指向element。
addAll(Collection
extendsE>c)
1.public boolean addAll(Collection
extends E> c) {
2. Object[] a = c.toArray();
3. int numNew = a.length;
4. ensureCapacity(size + numNew); // Increments modCount
5. System.arraycopy(a, 0, elementData, size, numNew);
6. size += numNew;
7. return numNew !
= 0;
8. }
先将集合c转换成数组,根据转换后数组的程度和ArrayList的size拓展容量,之后调用System.arraycopy方法复制元素到elementData的尾部,调整size。
根据返回的内容分析,只要集合c的大小不为空,即转换后的数组长度不为0则返回true。
addAll(intindex,Collection
extendsE>c)
1.public boolean addAll(int index, Collection
extends E> c) {
2. if (index > size || index < 0)
3. throw new IndexOutOfBoundsException(
4. "Index:
" + index + ", Size:
" + size);
5.
6. Object[] a = c.toArray();
7. int numNew = a.length;
8. ensureCapacity(size + numNew); // Increments modCount
9.
10. int numMoved = size - index;
11. if (numMoved > 0)
12. System.arraycopy(elementData, index, elementData, index + numNew,
13. numMoved);
14.
15. System.arraycopy(a, 0, elementData, index, numNew);
16. size += numNew;
17. return numNew !
= 0;
18. }
先判断index是否越界。
其他内容与addAll(Collection
extendsE>c)基本一致,只是复制的时候先将index开始的元素向后移动X(c转为数组后的长度)个位置(也是一个复制的过程),之后将数组内容复制到elementData的index位置至index+X。
clear()
1.public void clear() {
2. modCount++;
3.
4. // Let gc do its work
5. for (int i = 0; i < size; i++)
6. elementData[i] = null;
7.
8. size = 0;
9. }
clear的时候并没有修改elementData的长度(好不容易申请、拓展来的,凭什么释放,留着搞不好还有用呢。
这使得确定不再修改list内容之后最好调用trimToSize来释放掉一些空间),只是将所有元素置为null,size设置为0。
clone()
返回此ArrayList实例的浅表副本。
(不复制这些元素本身。
)
1.public Object clone() {
2. try {
3. ArrayList v = (ArrayList) super.clone();
4. v.elementData = Arrays.copyOf(elementData, size);
5. v.modCount = 0;
6. return v;
7. } catch (CloneNotSupportedException e) {
8. // this shouldn't happen, since we are Cloneable
9. throw new InternalError();
10. }
11. }
调用父类的clone方法返回一个对象的副本,将返回对象的elementData数组的内容赋值为原对象elementData数组的内容,将副本的modCount设置为0。
contains(Object)
1.public boolean contains(Object o) {
2. return indexOf(o) >= 0;
3. }
indexOf方法返回值与0比较来判断对象是否在list中。
接着看indexOf。
indexOf(Object)
1.public int indexOf(Object o) {
2. if (o == null) {
3. for (int i = 0; i < size; i++)
4. if (elementData[i]==null)
5. return i;
6. } else {
7. for (int i = 0; i < size; i++)
8. if (o.equals(elementData[i]))
9. return i;
10. }
11. return -1;
12. }
通过遍历elementData数组来判断对象是否在list中,若存在,返回index([0,size-1]),若不存在则返回-1。
所以contains方法可以通过indexOf(Object)方法的返回值来判断对象是否被包含在list中。
既然看了indexOf(Object)方法,接着就看lastIndexOf,光看名字应该就明白了返回的是传入对象在elementData数组中最后出现的index值。
1.public int lastIndexOf(Object o) {
2. if (o == null) {
3. for (int i = size-1; i >= 0; i--)
4. if (elementData[i]==null)
5. return i;
6. } else {
7. for (int i = size-1; i >= 0; i--)
8. if (o.equals(elementData[i]))
9. return i;
10. }
采用了从后向前遍历element数组,若遇到Objec