Java集合ArrayList的实现原理.docx
- 文档编号:26486193
- 上传时间:2023-06-19
- 格式:DOCX
- 页数:18
- 大小:24.92KB
Java集合ArrayList的实现原理.docx
《Java集合ArrayList的实现原理.docx》由会员分享,可在线阅读,更多相关《Java集合ArrayList的实现原理.docx(18页珍藏版)》请在冰豆网上搜索。
Java集合ArrayList的实现原理
Java集合---ArrayList的实现原理
目录:
一、 ArrayList概述
二、 ArrayList的实现
1)私有属性
2) 构造方法
3)元素存储
4)元素读取
5)元素删除
6)调整数组容量
7)转为静态数组toArray
总结
一、 ArrayList概述:
ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。
ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(Listl)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。
ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。
每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。
它总是至少等于列表的大小。
随着向ArrayList中不断添加元素,其容量也自动增长。
自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。
在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。
注意,此实现不是同步的。
如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。
二、 ArrayList的实现:
对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。
其操作基本上是对数组的操作。
下面我们来分析ArrayList的源代码:
1) 私有属性:
ArrayList定义只定义类两个私有属性:
/**
*ThearraybufferintowhichtheelementsoftheArrayListarestored.
*ThecapacityoftheArrayLististhelengthofthisarraybuffer.
*/
privatetransientObject[]elementData;
/**
*ThesizeoftheArrayList(thenumberofelementsitcontains).
*
*@serial
*/
privateintsize;
很容易理解,elementData存储ArrayList内的元素,size表示它包含的元素的数量。
有个关键字需要解释:
transient。
Java的serialization提供了一种持久化对象实例的机制。
当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。
为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。
有点抽象,看个例子应该能明白。
publicclassUserInfoimplementsSerializable{
privatestaticfinallongserialVersionUID=996890129747019948L;
privateStringname;
privatetransientStringpsw;
publicUserInfo(Stringname,Stringpsw){
this.name=name;
this.psw=psw;
}
publicStringtoString(){
return"name="+name+",psw="+psw;
}
}
publicclassTestTransient{
publicstaticvoidmain(String[]args){
UserInfouserInfo=newUserInfo("张三","123456");
System.out.println(userInfo);
try{
//序列化,被设置为transient的属性没有被序列化
ObjectOutputStreamo=newObjectOutputStream(newFileOutputStream(
"UserInfo.out"));
o.writeObject(userInfo);
o.close();
}catch(Exceptione){
//TODO:
handleexception
e.printStackTrace();
}
try{
//重新读取内容
ObjectInputStreamin=newObjectInputStream(newFileInputStream(
"UserInfo.out"));
UserInforeadUserInfo=(UserInfo)in.readObject();
//读取后psw的内容为null
System.out.println(readUserInfo.toString());
}catch(Exceptione){
//TODO:
handleexception
e.printStackTrace();
}
}
}
被标记为transient的属性在对象被序列化的时候不会被保存。
接着回到ArrayList的分析中......
2) 构造方法:
ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。
//ArrayList带容量大小的构造函数。
publicArrayList(intinitialCapacity){
super();
if(initialCapacity<0)
thrownewIllegalArgumentException("IllegalCapacity:
"+
initialCapacity);
//新建一个数组
this.elementData=newObject[initialCapacity];
}
//ArrayList无参构造函数。
默认容量是10。
publicArrayList(){
this(10);
}
//创建一个包含collection的ArrayList
publicArrayList(Collection
extendsE>c){
elementData=c.toArray();
size=elementData.length;
if(elementData.getClass()!
=Object[].class)
elementData=Arrays.copyOf(elementData,size,Object[].class);
}
3)元素存储:
ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection
extends E> c)、addAll(int index, Collection
extends E> c)这些添加元素的方法。
下面我们一一讲解:
20//用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。
21publicEset(intindex,Eelement){
22RangeCheck(index);
23
24EoldValue=(E)elementData[index];
25elementData[index]=element;
26returnoldValue;
27}
28//将指定的元素添加到此列表的尾部。
29publicbooleanadd(Ee){
30ensureCapacity(size+1);
31elementData[size++]=e;
32returntrue;
33}
34//将指定的元素插入此列表中的指定位置。
35//如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。
36publicvoidadd(intindex,Eelement){
37if(index>size||index<0)
38thrownewIndexOutOfBoundsException("Index:
"+index+",Size:
"+size);
39//如果数组长度不足,将进行扩容。
40ensureCapacity(size+1);//IncrementsmodCount!
!
41//将elementData中从Index位置开始、长度为size-index的元素,
42//拷贝到从下标为index+1位置开始的新的elementData数组中。
43//即将当前位于该位置的元素以及所有后续元素右移一个位置。
44System.arraycopy(elementData,index,elementData,index+1,size-index);
45elementData[index]=element;
46size++;
47}
48//按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的尾部。
49publicbooleanaddAll(Collection
extendsE>c){
50Object[]a=c.toArray();
51intnumNew=a.length;
52ensureCapacity(size+numNew);//IncrementsmodCount
53System.arraycopy(a,0,elementData,size,numNew);
54size+=numNew;
55returnnumNew!
=0;
56}
57//从指定的位置开始,将指定collection中的所有元素插入到此列表中。
58publicbooleanaddAll(intindex,Collection
extendsE>c){
59if(index>size||index<0)
60thrownewIndexOutOfBoundsException(
61"Index:
"+index+",Size:
"+size);
62
63Object[]a=c.toArray();
64intnumNew=a.length;
65ensureCapacity(size+numNew);//IncrementsmodCount
66
67intnumMoved=size-index;
68if(numMoved>0)
69System.arraycopy(elementData,index,elementData,index+numNew,numMoved);
70
71System.arraycopy(a,0,elementData,index,numNew);
72size+=numNew;
73returnnumNew!
=0;
}
书上都说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)中了。
4)元素读取:
//返回此列表中指定位置上的元素。
publicEget(intindex){
RangeCheck(index);
return(E)elementData[index];
}
5)元素删除:
ArrayList提供了根据下标或者指定对象两种方式的删除功能。
如下:
romove(intindex):
1//移除此列表中指定位置上的元素。
2publicEremove(intindex){
3RangeCheck(index);
4
5modCount++;
6EoldValue=(E)elementData[index];
7
8intnumMoved=size-index-1;
9if(numMoved>0)
10System.arraycopy(elementData,index+1,elementData,index,numMoved);
11elementData[--size]=null;//Letgcdoitswork
12
13returnoldValue;
14}
首先是检查范围,修改modCount,保留将要被移除的元素,将移除位置之后的元素向前挪动一个位置,将list末尾元素置空(null),返回被移除的元素。
remove(Objecto)
1//移除此列表中首次出现的指定元素(如果存在)。
这是应为ArrayList中允许存放重复的元素。
2publicbooleanremove(Objecto){
3//由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。
4if(o==null){
5for(intindex=0;index 6if(elementData[index]==null){ 7//类似remove(intindex),移除列表中指定位置上的元素。 8fastRemove(index); 9returntrue; 10} 11}else{ 12for(intindex=0;index 13if(o.equals(elementData[index])){ 14fastRemove(index); 15returntrue; 16} 17} 18returnfalse; 19} 20} 首先通过代码可以看到,当移除成功后返回true,否则返回false。 remove(Objecto)中通过遍历element寻找是否存在传入对象,一旦找到就调用fastRemove移除对象。 为什么找到了元素就知道了index,不通过remove(index)来移除元素呢? 因为fastRemove跳过了判断边界的处理,因为找到元素就相当于确定了index不会超过边界,而且fastRemove并不返回被移除的元素。 下面是fastRemove的代码,基本和remove(index)一致。 1privatevoidfastRemove(intindex){ 2modCount++; 3intnumMoved=size-index-1; 4if(numMoved>0) 5System.arraycopy(elementData,index+1,elementData,index, 6numMoved); 7elementData[--size]=null;//Letgcdoitswork 8} removeRange(intfromIndex,inttoIndex) 1protectedvoidremoveRange(intfromIndex,inttoIndex){ 2modCount++; 3intnumMoved=size-toIndex; 4System.arraycopy(elementData,toIndex,elementData,fromIndex, 5numMoved); 6 7//Letgcdoitswork 8intnewSize=size-(toIndex-fromIndex); 9while(size! =newSize) 10elementData[--size]=null; 11} 执行过程是将elementData从toIndex位置开始的元素向前移动到fromIndex,然后将toIndex位置之后的元素全部置空顺便修改size。 这个方法是protected,及受保护的方法,为什么这个方法被定义为protected呢? 这是一个解释,但是可能不容易看明白。 先看下面这个例子 ArrayList 3,4,5,6)); //fromIndexlowendpoint(inclusive)ofthesubList //toIndexhighendpoint(exclusive)ofthesubList ints.subList(2,4).clear(); System.out.println(ints); 输出结果是[0,1,4,5,6],结果是不是像调用了removeRange(intfromIndex,inttoIndex)! 哈哈哈,就是这样的。 但是为什么效果相同呢? 是不是调用了removeRange(intfromIndex,inttoIndex)呢? 6) 调整数组容量ensureCapacity: 从上面介绍的向ArrayList中存储元素的代码中,我们看到,每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。 数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。 在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。 publicvoidensureCapacity(intminCapacity){ modCount++; intoldCapacity=elementData.length; if(minCapacity>oldCapacity){ ObjectoldData[]=elementData; intnewCapacity=(oldCapacity*3)/2+1;//增加50%+1 if(newCapacity newCapacity=minCapacity; //minCapacityisusuallyclosetosize,sothisisawin: elementData=Arrays.copyOf(elementData,newCapacity); } } 从上述代码中可以看出,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。 这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。 当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。 或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。 Object oldData[] = 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,
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java 集合 ArrayList 实现 原理