Java Collection Framework List.docx
- 文档编号:29184833
- 上传时间:2023-07-21
- 格式:DOCX
- 页数:31
- 大小:164.82KB
Java Collection Framework List.docx
《Java Collection Framework List.docx》由会员分享,可在线阅读,更多相关《Java Collection Framework List.docx(31页珍藏版)》请在冰豆网上搜索。
JavaCollectionFrameworkList
JavaCollectionFramework:
List
摘要:
List是JavaCollectionFramework的重要成员,具体包括List接口及其所有的实现类。
由于List接口继承了Collection接口,所以List拥有Collection的所有操作。
同时,又因为List是列表类型,所以List本身还提供了一些适合自身的方法。
ArrayList是一个动态数组,实现了数组动态扩容,随机访问效率高;LinkedList是一个双向链表,随机插入和删除效率高,可用作队列的实现。
一.要点
List基础特性与框架
ArrayList:
动态数组
LinkedList:
双向循环链表
二.List基础特性与框架
1、List特色操作
List包括List接口以及List接口的所有实现类(ArrayList,LinkedList,Vector,Stack),其中Vector和Stack已过时。
因为List接口实现了Collection接口(如代码1所示),所以List接口拥有Collection接口提供的所有常用方法,同时,又因为List是列表类型,所以List接口还提供了一些适合于自身的常用方法,如表1所示。
//代码1
publicinterfaceList
特别地,对于List而言:
AbstractList给出了iterator()方法的通用实现,其中的next()和remove()方法底层分别由get(intindex)和remove(intindex)方法实现;
对于subList方法,原list和子list做的非结构性修改(不涉及到list的大小改变的修改),都会影响到彼此;对于结构性修改:
若发生结构性修改的是返回的子list,那么原list的大小也会发生变化(modCount与expectedModCount同时变化,不会触发Fast-Fail机制);而若发生结构性修改的是原list(不包括由返回的子list导致的改变),那么判断式l.modCount!
=expectedModCount将返回TRUE,触发Fast-Fail机制,抛出ConcurrentModificationException。
因此,若在调用sublist返回了子list之后,修改原list的大小,那么之前产生的子list将会失效;
删除一个list的某个区段:
list.subList(from,to).clear();
2、List结构
下面我们对List结构图中所涉及到的类加以介绍:
AbstractList是一个抽象类,它实现List接口并继承于AbstractCollection。
对于“按顺序遍历访问元素”的需求,使用List的Iterator即可以做到,抽象类AbstractList提供该实现;而访问特定位置的元素(也即按索引访问)、元素的增加和删除涉及到了List中各个元素的连接关系,并没有在AbstractList中提供实现(List的类型不同,其实现方式也随之不同,所以将具体实现放到子类);
AbstractSequentialList是一个抽象类,它继承于AbstractList。
AbstractSequentialList通过ListIterator实现了“链表中,根据index索引值操作链表的全部函数”。
此外,ArrayList通过System.arraycopy(完成元素的挪动)实现了“顺序表中,根据index索引值操作顺序表的全部函数”;
ArrayList,LinkedList,Vector,Stack是List的4个实现类;
ArrayList是一个动态数组。
它由数组实现,随机访问效率高,随机插入、随机删除效率低;
LinkedList是一个双向链表(顺序表)。
LinkedList随机访问效率低,但随机插入、随机删除效率高,。
可以被当作堆栈、队列或双端队列进行操作;
Vector是矢量队列,和ArrayList一样,它也是一个动态数组,由数组实现。
但ArrayList是非线程安全的,而Vector是线程安全的;
Stack是栈,它继承于Vector。
它的特性是:
先进后出(FILO,FirstInLastOut).
3、List特性:
Java中的List是对数组的有效扩展,它是这样一种结构:
如果不使用泛型,它可以容纳任何类型的元素,如果使用泛型,那么它只能容纳泛型指定的类型的元素。
和数组(数组不支持泛型)相比,List的容量是可以动态扩展的;
List中的元素是“有序”的。
这里的“有序”,并不是排序的意思,而是说我们可以对某个元素在集合中的位置进行指定,包括对列表中每个元素的插入位置进行精确地控制、根据元素的整数索引(在列表中的位置)访问元素和搜索列表中的元素;
List中的元素是可以重复的,因为其为有序的数据结构;
List中常用的集合对象包括:
ArrayList、Vector和LinkedList,其中前两者是基于数组来进行存储,后者是基于链表进行存储。
其中Vector是线程安全的,其余两个不是线程安全的;
List中是可以包括null的,即使使用了泛型;
List接口提供了特殊的迭代器,称为ListIterator,除了允许Iterator接口提供的正常操作外,该迭代器还允许元素插入和替换,以及双向访问。
三.ArrayList
1、ArrayList基础
ArrayList实现了List中所有可选操作,并允许包括NULL在内的所有元素。
除了实现List接口外,此类还提供一些方法来操作其支撑数组的大小。
ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,并且用size属性来标识该容器里的元素个数,而非这个被包装数组的大小。
每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小,并且它总是至少等于列表的大小。
随着向ArrayList中不断添加元素,其容量也自动增长。
自动增长会带来数据向新数组的重新拷贝。
因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。
在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。
注意,此实现不是同步的。
如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改.)了列表,那么它必须保持外部同步。
ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输。
阅读源码可以发现,ArrayList内置的数组用transient关键字修饰,以示其不会被序列化。
当然,ArrayList的元素最终还是会被序列化的,在序列化/反序列化时,会调用ArrayList的writeObject()/readObject()方法,将该ArrayList中的元素(即0…size-1下标对应的元素)和容量大小写入流/从流读出。
这样做的好处是,只保存/传输有实际意义的元素,最大限度的节约了存储、传输和处理的开销。
ArrayList实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问(于是否支持get(index)访问不同)。
RandomAccess接口是List实现所使用的标记接口,用来表明其支持快速(通常是固定时间)随机访问。
此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。
特别地,在对List的遍历算法中,要尽量来判断是属于RandomAccess(如ArrayList)还是SequenceAccess(如LinkedList),因为适合RandomAccessList的遍历算法,用在SequenceAccessList上就差别很大,即对于实现了RandomAccess接口的类实例而言,此循环
for(inti=0,i list.get(i); 的运行速度要快于以下循环: for(Iteratori=list.iterator();i.hasNext();) i.next(); 所以,我们在遍历List之前,可以用if(listinstanceofRamdomAccess)来判断一下,选择用哪种遍历方式。 ArrayList实现了Cloneable接口,能被克隆。 Cloneable接口里面没有任何方法,只是起一个标记作用,表明当一个类实现了该接口时,该类可以通过调用clone()方法来克隆该类的实例。 ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(Listl)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。 2、ArrayList在JDK中的定义 我们可以从ArrayList的源码看到,其包括两个域和三个构造函数,源码如下: privatetransientObject[]elementData: 支撑数组 privateintsize: 元素个数 publicArrayList(intinitialCapacity){…}: 给定初始容量的构造函数; publicArrayList(){…}: JavaCollectionFramework规范-默认无参的构造函数(初始容量指定为10); publicArrayList(Collectionc extendsE>){…}: JavaCollectionFramework规范-参数为指定容器的构造函数 publicclassArrayList implementsList { privatestaticfinallongserialVersionUID=8683452581122892189L; /** *ThearraybufferintowhichtheelementsoftheArrayListarestored.(ArrayList支撑数组) *ThecapacityoftheArrayLististhelengthofthisarraybuffer.(ArrayList容量的定义) */ privatetransientObject[]elementData;//瞬时域 /** *ThesizeoftheArrayList(thenumberofelementsitcontains).(ArrayList大小的定义) */ privateintsize; /** *Constructsanemptylistwiththespecifiedinitialcapacity */ publicArrayList(intinitialCapacity){//给定初始容量的构造函数 super(); if(initialCapacity<0) thrownewIllegalArgumentException("IllegalCapacity: "+ initialCapacity); this.elementData=newObject[initialCapacity];//泛型与数组不兼容 } /** *Constructsanemptylistwithaninitialcapacityoften. */ publicArrayList(){//JavaCollectionFramework规范: 默认无参的构造函数 this(10); } /** *Constructsalistcontainingtheelementsofthespecified *collection,intheordertheyarereturnedbythecollection's *iterator. */ publicArrayList(Collection extendsE>c){//JavaCollectionFramework规范: 参数为指定容器的构造函数 elementData=c.toArray(); size=elementData.length; //c.toArraymight(incorrectly)notreturnObject[](see6260652) if(elementData.getClass()! =Object[].class) elementData=Arrays.copyOf(elementData,size,Object[].class); } 3、ArrayList基本操作的保证 边界检查(即检查ArrayList的Size): 涉及到index的操作 //边界检查 privatevoidRangeCheck(intindex){ if(index>=size) thrownewIndexOutOfBoundsException( "Index: "+index+",Size: "+size); } 调整数组容量(增加容量): 向ArrayList中增加元素 //调整数组容量 publicvoidensureCapacity(intminCapacity){ modCount++; intoldCapacity=elementData.length; if(minCapacity>oldCapacity){ ObjectoldData[]=elementData; intnewCapacity=(oldCapacity*3)/2+1; if(newCapacity newCapacity=minCapacity; //minCapacityisusuallyclosetosize,sothisisawin: elementData=Arrays.copyOf(elementData,newCapacity); } } 向ArrayList中增加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度。 如果超出,ArrayList将会进行扩容,以满足添加数据的需求。 数组扩容通过一个public方法ensureCapacity(intminCapacity)来实现: 在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。 ArrayList进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长为其原容量的1.5倍+1。 这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。 当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。 或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。 在ensureCapacity的源代码中有这样一段代码: ObjectoldData[]=elementData;//为什么要用到oldData[] 乍一看,后面并没有用到oldData,这句话显得多此一举! 但是这牵涉到了一个内存管理的问题.而且,为什么这一句还在if的内部? 这是跟elementData=Arrays.copyOf(elementData,newCapacity);这句是有关系的,在下面这句Arrays.copyOf 的实现时,新创建了newCapacity大小的内存,然后把老的elementData放入。 这样,由于旧的内存的引用是elementData,而elementData指向了新的内存块,如果有一个局部变量oldData变量引用旧的内存块的话,在copy的过程中就会比较安全,因为这样证明这块老的内存依然有引用,分配内存的时候就不会被侵占掉。 然后,在copy完成后,这个局部变量的生命周期也过去了,此时释放才是安全的。 否则的话,在copy的时候万一新的内存或其他线程的分配内存侵占了这块老的内存,而copy还没有结束,这将是个严重的事情。 调整数组容量(减少容量): 将底层数组的容量调整为当前列表保存的实际元素的大小 在使用ArrayList过程中,由于elementData的长度会被拓展,所以经常会出现size很小但elementData.length很大的情况,造成空间的浪费。 ArrayList通过trimToSize方法返回一个新的数组给elementData,其中: 元素内容保持不变,length和size相同。 publicvoidtrimToSize(){ modCount++; intoldCapacity=elementData.length; if(size elementData=Arrays.copyOf(elementData,size); } } Fail-Fast机制 动机: 在JavaCollection中,为了防止在某个线程在对Collection进行迭代时,其他线程对该Collection进行结构上的修改。 换句话说,迭代器的快速失败行为仅用于检测代码的bug。 本质: Fail-Fast是Java集合的一种错误检测机制。 作用场景: 在使用迭代器时,Collection的结构发生变化,抛出ConcurrentModificationException。 当然,这并不能说明Collection对象已经被不同线程并发修改,因为如果单线程违反了规则,同样也有会抛出该异常。 当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。 例如: 假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会触发fail-fast机制,抛出ConcurrentModificationException异常。 在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。 我们知道fail-fast产生的原因就在于: 程序在对collection进行迭代时,某个线程对该collection在结构上对其做了修改。 要想进一步了解fail-fast机制,我们首先要对ConcurrentModificationException异常有所了解。 当方法检测到对象的并发修改,但不允许这种修改时就抛出该异常。 同时需要注意的是,该异常不会始终指出对象已经由不同线程并发修改,如果单线程违反了规则,同样也有可能会抛出改异常。 诚然,迭代器的快速失败行为无法得到保证,它不能保证一定会出现该错误,但是快速失败操作会尽最大努力抛出ConcurrentModificationException异常,所以,为提高此类操作的正确性而编写一个依赖于此异常的程序是错误的做法,正确做法是: ConcurrentModificationException应该仅用于检测bug。 下面我们以ArrayList为例进一步分析fail-fast产生的原因: privateclassItrimplementsIterator intcursor; intlastRet=-1; intexpectedModCount=ArrayList.this.modCount; publicbooleanhasNext(){ return(this.cursor! =ArrayList.this.size); } publicEnext(){ checkForComodification(); /**省略此处代码*/ } publicvoidremove(){ if(this.lastRet<0) thrownewIllegalStateException(); checkForComodification(); /**省略此处代码*/ } finalvoidcheckForComodification(){ if(ArrayList.this.modCount==this.expectedModCount) return; thrownewConcurrentModificationException(); } } 从上面的源代码我们可以看出,迭代器在调用next()、remove()方法时都是调用checkForComodification()方法,该方法用于判断“modCount==expectedModCount”: 若不等,触发fail-fast机制,抛出ConcurrentModificationException异常。 所以,要弄清楚为什么会产生fail-fast机制,我们就必须要弄明白“modCount! =expectedModCount”什么时候发生,换句话说,他们的值在什么时候发生改变的。 expectedModCount是在Itr中定义的: “intexpectedModCount=ArrayList.this.modCount;”,所以它的值是不可能会修改的,所以会变的就是modC
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java Collection Framework List