Java集合ArrayList的实现原理Word文档格式.docx
《Java集合ArrayList的实现原理Word文档格式.docx》由会员分享,可在线阅读,更多相关《Java集合ArrayList的实现原理Word文档格式.docx(11页珍藏版)》请在冰豆网上搜索。
privatetransientObject[]elementData;
*ThesizeoftheArrayList(thenumberofelementsitcontains).
*
*@serial
privateintsize;
elementData存储ArrayList内的元素,size表示它包含的元素的数量。
有个关键字需要解释:
transient。
Java的serialization提供了一种持久化对象实例的机制。
当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。
为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。
构造方法
ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。
*Constructsanemptylistwiththespecifiedinitialcapacity.
*@paraminitialCapacitytheinitialcapacityofthelist
*@throwsIllegalArgumentExceptionifthespecifiedinitialcapacity
*isnegative
publicArrayList(intinitialCapacity){
super();
if(initialCapacity<
0)
thrownewIllegalArgumentException("
IllegalCapacity:
"
+
initialCapacity);
this.elementData=newObject[initialCapacity];
}
*Constructsanemptylistwithaninitialcapacityoften.
publicArrayList(){
this.elementData=EMPTY_ELEMENTDATA;
*Constructsalistcontainingtheelementsofthespecified
*collection,intheordertheyarereturnedbythecollection'
s
*iterator.
*@paramcthecollectionwhoseelementsaretobeplacedintothislist
*@throwsNullPointerExceptionifthespecifiedcollectionisnull
publicArrayList(Collection<
?
extendsE>
c){
elementData=c.toArray();
size=elementData.length;
//c.toArraymight(incorrectly)notreturnObject[](see6260652)
if(elementData.getClass()!
=Object[].class)
elementData=Arrays.copyOf(elementData,size,Object[].class);
元素存储
ArrayList提供了set(intindex,Eelement)、add(Ee)、add(intindex,Eelement)、addAll(Collection
//用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。
publicEset(intindex,Eelement){
RangeCheck(index);
//范围检查
EoldValue=(E)elementData[index];
elementData[index]=element;
returnoldValue;
}
//将指定的元素添加到此列表的尾部。
publicbooleanadd(Ee){
ensureCapacity(size+1);
elementData[size++]=e;
returntrue;
//将指定的元素插入此列表中的指定位置。
//如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。
publicvoidadd(intindex,Eelement){
if(index>
size||index<
0)
thrownewIndexOutOfBoundsException("
Index:
+index+"
Size:
+size);
//如果数组长度不足,将进行扩容。
ensureCapacity(size+1);
//IncrementsmodCount!
!
//将elementData中从Index位置开始、长度为size-index的元素,
//拷贝到从下标为index+1位置开始的新的elementData数组中。
//即将当前位于该位置的元素以及所有后续元素右移一个位置。
System.arraycopy(elementData,index,elementData,index+1,size-index);
size++;
//按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的尾部。
publicbooleanaddAll(Collection<
c){
Object[]a=c.toArray();
intnumNew=a.length;
ensureCapacity(size+numNew);
//IncrementsmodCount
System.arraycopy(a,0,elementData,size,numNew);
size+=numNew;
returnnumNew!
=0;
//从指定的位置开始,将指定collection中的所有元素插入到此列表中。
publicbooleanaddAll(intindex,Collection<
thrownewIndexOutOfBoundsException(
+index+"
+size);
intnumMoved=size-index;
if(numMoved>
System.arraycopy(elementData,index,elementData,index+numNew,numMoved);
System.arraycopy(a,0,elementData,index,numNew);
元素读取
//返回此列表中指定位置上的元素。
publicEget(intindex){
return(E)elementData[index];
元素删除
romove(intindex):
//移除此列表中指定位置上的元素。
publicEremove(intindex){
modCount++;
intnumMoved=size-index-1;
System.arraycopy(elementData,index+1,elementData,index,numMoved);
elementData[--size]=null;
//Letgcdoitswork
首先是检查范围,修改modCount,保留将要被移除的元素,将移除位置之后的元素向前挪动一个位置,将list末尾元素置空(null),返回被移除的元素。
remove(Objecto)
//移除此列表中首次出现的指定元素(如果存在)。
这是应为ArrayList中允许存放重复的元素。
publicbooleanremove(Objecto){
//由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。
if(o==null){
for(intindex=0;
index<
size;
index++)
if(elementData[index]==null){
//类似remove(intindex),移除列表中指定位置上的元素。
fastRemove(index);
}
}else{
if(o.equals(elementData[index])){
returnfalse;
}
首先通过代码可以看到,当移除成功后返回true,否则返回false。
remove(Objecto)中通过遍历element寻找是否存在传入对象,一旦找到就调用fastRemove移除对象。
为什么找到了元素就知道了index,不通过remove(index)来移除元素呢?
因为fastRemove跳过了判断边界的处理,因为找到元素就相当于确定了index不会超过边界,而且fastRemove并不返回被移除的元素。
下面是fastRemove的代码,基本和remove(index)一致。
privatevoidfastRemove(intindex){
System.arraycopy(elementData,index+1,elementData,index,
numMoved);
removeRange(intfromIndex,inttoIndex)
protectedvoidremoveRange(intfromIndex,inttoIndex){
intnumMoved=size-toIndex;
System.arraycopy(elementData,toIndex,elementData,fromIndex,
intnewSize=size-(toIndex-fromIndex);
while(size!
=newSize)
执行过程是将elementData从toIndex位置开始的元素向前移动到fromIndex,然后将toIndex位置之后的元素全部置空顺便修改size。
这个方法是protected,及受保护的方法,为什么这个方法被定义为protected呢?
这是一个解释,但是可能不容易看明白。
先看下面这个例子:
ArrayList<
Integer>
ints=newArrayList<
(Arrays.asList(0,1,2,
3,4,5,6));
//fromIndexlowendpoint(inclusive)ofthesubList
//toIndexhighendpoint(exclusive)ofthesubList
ints.subList(2,4).clear();
System.out.println(ints);
输出结果是[0,1,4,5,6]
调整数组容量ensureCapacity
从上面介绍的向ArrayList中存储元素的代码中,我们看到,每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。
数组扩容通过一个公开的方法ensureCapacity(intminCapacity)来实现。
在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。
publicvoidensureCapacity(intminCapacity){
intoldCapacity=elementData.length;
if(minCapacity>
oldCacity){
ObjectoldData[]=elementData;
intnewCapacity=(oldCapacity*3)/2+1;
//增加50%+1
if(newCapacity<
minCapacity)
newCapacity=minCapacity;
//minCapacityisusuallyclosetosize,sothisisawin:
elementData=Arrays.copyOf(elementData,newCapacity);
从上述代码中可以看出,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。
这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。
当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。
或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。
ObjectoldData[]=elementData;
//为什么要用到oldData[]
乍一看来后面并没有用到关于oldData,这句话显得多此一举!
但是这是一个牵涉到内存管理的类,所以要了解内部的问题。
而且为什么这一句还在if的内部,这跟elementData=Arrays.copyOf(elementData,newCapacity);
这句是有关系的,下面这句Arrays.copyOf的实现时新创建了newCapacity大小的内存,然后把老的elementData放入。
好像也没有用到oldData,有什么问题呢。
问题就在于旧的内存的引用是elementData,elementData指向了新的内存块,如果有一个局部变量oldData变量引用旧的内存块的话,在copy的过程中就会比较安全,因为这样证明这块老的内存依然有引用,分配内存的时候就不会被侵占掉,然后copy完成后这个局部变量的生命期也过去了,然后释放才是安全的。
不然在copy的的时候万一新的内存或其他线程的分配内存侵占了这块老的内存,而copy还没有结束,这将是个严重的事情。
关于ArrayList和Vector区别如下:
ArrayList在内存不够时默认是扩展50%+1个,Vector是默认扩展1倍。
Vector提供indexOf(obj,start)接口,ArrayList没有。
Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销。
ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。
它可以通过trimToSize方法来实现。
代码如下:
publicvoidtrimToSize(){
if(size<
oldCapacity){
elementData=Arrays.copyOf(elementData,size);
由于elementData的长度会被拓展,size标记的是其中包含的元素的个数。
所以会出现size很小但elementData.length很大的情况,将出现空间的浪费。
trimToSize将返回一个新的数组给elementData,元素内容保持不变,length和size相同,节省空间。
转为静态数组toArray
注意ArrayList的两个转化为静态数组的toArray方法。
第一个,调用Arrays.copyOf将返回一个数组,数组内容是size个elementData的元素,即拷贝elementData从0至size-1位置的元素到新数组并返回。
publicObject[]toArray(){
returnArrays.copyOf(elementData,size);
第二个,如果传入数组的长度小于size,返回一个新的数组,大小为size,类型与传入数组相同。
所传入数组长度与size相等,则将elementData复制到传入数组中并返回传入的数组。
若传入数组长度大于size,除了复制elementData外,还将把返回数组的第size个元素置为空。
public<
T>
T[]toArray(T[]a){
if(a.length<
size)
//Makeanewarrayofa'
sruntimetype,butmycontents:
return(T[])Arrays.copyOf(elementData,size,a.getClass());
System.arraycopy(elementData,0,a,0,size);
if(a.length>
a[size]=null;
returna;
总结:
关于ArrayList的源码,给出几点比较重要的总结:
1、注意其三个不同的构造方法。
无参构造方法构造的ArrayList的容量默认为10,带有Collection参数的构造方法,将Collection转化为数组赋给ArrayList的实现数组elementData。
2、注意扩充容量的方法ensureCapacity。
ArrayList在每次增加元素(可能是1个,也可能是一组)时,都要调用该方法来确保足够的容量。
当容量不足以容纳当前的元素个数时,就设置新的容量为旧的容量的1.5倍加1,如果设置后的新容量还不够,则直接新容量设置为传入的参数(也就是所需的容量),而后用Arrays.copyof()方法将元素拷贝到新的数组(详见下面的第3点)。
从中可以看出,当容量不够时,每次增加元素,都要将原来的元素拷贝到一