java1.8 常用集合源码学习:ArrayList

来源:互联网 发布:python编写桌面程序 编辑:程序博客网 时间:2024/05/29 07:51
1、api

List 接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是不同步的。)
sizeisEmptygetsetiterator 和 listIterator 操作都以固定时间运行。add 操作以分摊的固定时间 运行,也就是说,添加 n 个元素需要 O(n) 时间。其他所有操作都以线性时间运行(大体上讲)。与用于 LinkedList 实现的常数因子相比,此实现的常数因子较低。
每个 ArrayList 实例都有一个容量。该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。并未指定增长策略的细节,因为这不只是添加元素会带来分摊固定时间开销那样简单。
在添加大量元素前,应用程序可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。
注意,此实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须 保持外部同步。(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法将该列表“包装”起来。这最好在创建时完成,以防止意外对列表进行不同步的访问:
List list = Collections.synchronizedList(new ArrayList(...));
此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:在创建迭代器之后,除非通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug。
此类是 Java Collections Framework 的成员。

2、源码学习:

默认容量10
private static final intDEFAULT_CAPACITY=10;

所有空的list共享的一个空数组
private static finalObject[]EMPTY_ELEMENTDATA= {};

所有空的list共享的一个空数组(专门给默认容量的空数组使用)
private static finalObject[]DEFAULTCAPACITY_EMPTY_ELEMENTDATA= {};

真正存储数据的数组
transientObject[]elementData;

集合的大小
private intsize;

用已有的集合来构造ArrayList,会将该集合转换成数组,toArray方法其实是调用了该集合的iterator方法,所以这个遍历顺序每个种类的集合是不一样的
publicArrayList(Collection<?extendsE> c) {
elementData= c.toArray();
if((size=elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if(elementData.getClass() != Object[].class)
elementData= Arrays.copyOf(elementData,size,Object[].class);
}else{
// replace with empty array.
this.elementData=EMPTY_ELEMENTDATA;
}
}

trimToSize方法将此 ArrayList 实例的容量调整为列表的当前大小。底层是调用了System.arraycopy将elementData拷贝了一份,并且只拷贝了size的长度(即有数据的部分)
public voidtrimToSize() {
modCount++;
if(size<elementData.length) {
elementData= (size==0)
?EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData,size);
}
}

方法确保list有足够的容量,如果没有足够容量会进行扩容,调用ensureExplicitCapacity方法。在这里只要elementData 不是默认容量(10)的空数组,都会调用到ensureExplicitCapacity方法。如果是默认容量的空数组则会比较一下传入的minCapacity是否大于10
public voidensureCapacity(intminCapacity) {
intminExpand = (elementData!=DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
?0
// larger than default for default empty table. It's already
// supposed to be at default size.
:DEFAULT_CAPACITY;

if(minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}

ensureExplicitCapacity方法简单判断了一下传入的容量是否大于当前存储数据的elementData数组的长度,如果大于这个长度才调用grow方法真正扩大数组的长度
private voidensureExplicitCapacity(intminCapacity) {
modCount++;

// overflow-conscious code
if(minCapacity -elementData.length>0)
grow(minCapacity);
}




private voidgrow(intminCapacity) {
// overflow-conscious code
intoldCapacity =elementData.length;
intnewCapacity = oldCapacity + (oldCapacity >>1);
if(newCapacity - minCapacity <0)
newCapacity = minCapacity;
if(newCapacity -MAX_ARRAY_SIZE>0)
newCapacity =hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData= Arrays.copyOf(elementData,newCapacity);
}

数组的最大长度比Integer的最大值小8,这是因为有些vm会在数组中保存一些头信息
private static final intMAX_ARRAY_SIZE= Integer.MAX_VALUE-8;

grow方法实际最后调用了Arrays.copyOf(elementData, newCapacity);方法重新复制了一个数组,除了拷贝了元数据外,还把长度扩大到了newCapacity。newCapacity实际有可能为3个值:原数组长度的1.5倍;传入的长度;MAX_ARRAY_SIZE的长度。具体的确定规则为,前两者取大的,然后再和最后的MAX_ARRAY_SIZE相比取小的。在这里如果oldCapacity 足够大oldCapacity + (oldCapacity >> 1)操作是有可能数据溢出变成负数的。
private voidgrow(intminCapacity) {
// overflow-conscious code
intoldCapacity =elementData.length;
intnewCapacity = oldCapacity + (oldCapacity >>1);
if(newCapacity - minCapacity <0)
newCapacity = minCapacity;
if(newCapacity -MAX_ARRAY_SIZE>0)
newCapacity =hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData= Arrays.copyOf(elementData,newCapacity);
}

contains方法和HashMap等类的实现不同,这里是调用了indexOf方法,返回的索引大于等于0则说明该项是存在的
public booleancontains(Object o) {
returnindexOf(o) >=0;
}

indexOf方法是要遍历整个数组的,所以这个方法和所有基于它的方法的耗时都和整个数组的长度有关。这里是用size去遍历,所以如果是空的ArrayList,即使内部的数组已经初始化过,也不会进行遍历数组的操作。
public intindexOf(Object o) {
if(o ==null) {
for(inti =0;i <size;i++)
if(elementData[i]==null)
returni;
}else{
for(inti =0;i <size;i++)
if(o.equals(elementData[i]))
returni;
}
return-1;
}

只是和indexOf的遍历顺序相反而已,从最后开始遍历
public intlastIndexOf(Object o) {
if(o ==null) {
for(inti =size-1;i >=0;i--)
if(elementData[i]==null)
returni;
}else{
for(inti =size-1;i >=0;i--)
if(o.equals(elementData[i]))
returni;
}
return-1;
}

这里的clone也是浅拷贝
publicObjectclone() {
try{
ArrayList<?> v = (ArrayList<?>)super.clone();
v.elementData= Arrays.copyOf(elementData,size);
v.modCount=0;
returnv;
}catch(CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw newInternalError(e);
}
}

在这个方法中,如果a.lenth<当前list的长度,则直接返回一个用当前list拷贝的数组。反之,则将当前数组拷贝到传入数组a中,并且将拷贝结束后的位置的值置为null
public<T>T[]toArray(T[] a) {
if(a.length<size)
// Make a new array of a's runtime type, but my contents:
return(T[]) Arrays.copyOf(elementData,size,a.getClass());
System.arraycopy(elementData,0,a,0,size);
if(a.length>size)
a[size] = null;
returna;
}

get方法在检查完index是否合法后,调用elementData方法从数组中取出数据,非常高效
publicEget(intindex) {
rangeCheck(index);

returnelementData(index);
}
EelementData(intindex) {
return(E)elementData[index];
}
private voidrangeCheck(intindex) {
if(index >=size)
throw newIndexOutOfBoundsException(outOfBoundsMsg(index));
}

set方法也是高效的,因为可以直接查找到oldValue并且替换值。注意如果设置list的size以外的值会在rangeCheck方法中报错
publicEset(intindex,Eelement) {
rangeCheck(index);

EoldValue = elementData(index);
elementData[index] = element;
returnoldValue;
}

add也很简单,直接将值加到队尾。注意在ensureCapacityInternal方法中会将modCount+1
public booleanadd(Ee) {
ensureCapacityInternal(size+1);// Increments modCount!!
elementData[size++] = e;
return true;
}

add方法首先判断index的范围,确保list容量足够,然后将数组中从index开始的数据都向后移动一位,然后将index位置设置成插入的element
public voidadd(intindex,Eelement) {
rangeCheckForAdd(index);

ensureCapacityInternal(size+1);// Increments modCount!!
System.arraycopy(elementData,index,elementData,index +1,
size- index);
elementData[index] = element;
size++;
}

remove方法类似,先取得index位置的value,用于删除后返回。然后把index+1开始的所有数据左移一位,然后size-1,并且将数组中size-1的位置设置为null
publicEremove(intindex) {
rangeCheck(index);

modCount++;
EoldValue = elementData(index);

intnumMoved =size- index -1;
if(numMoved >0)
System.arraycopy(elementData,index+1,elementData,index,
numMoved);
elementData[--size] = null;// clear to let GC do its work

returnoldValue;
}

如果是删除一个对象,则像查找一样,需要遍历数组查找第一个和它相同的对象的index,然后调用fastRemove直接将它删除,fastRemove和remove非常类似,只是不做过多的判断,效率更高,但只能内部使用
public booleanremove(Object o) {
if(o ==null) {
for(intindex =0;index <size;index++)
if(elementData[index] == null) {
fastRemove(index);
return true;
}
}else{
for(intindex =0;index <size;index++)
if(o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

private voidfastRemove(intindex) {
modCount++;
intnumMoved =size- index -1;
if(numMoved >0)
System.arraycopy(elementData,index+1,elementData,index,
numMoved);
elementData[--size] = null;// clear to let GC do its work
}

addAll方法也是调用的底层的System.arraycopy方法,而非在这个方法内部遍历原集合
public booleanaddAll(Collection<?extendsE> c) {
Object[] a = c.toArray();
intnumNew = a.length;
ensureCapacityInternal(size+ numNew);// Increments modCount
System.arraycopy(a,0,elementData,size,numNew);
size+= numNew;
returnnumNew !=0;
}

如果在addAll时指定了要插入到原list的位置,则方法内部需要调用两次System.arraycopy,第一次将elementData的index位置开始的数据向后移动,为待插入的集合腾出位置,然后再调用一次,将待插入集合所有数据复制到从index开始的区域中
public booleanaddAll(intindex,Collection<?extendsE> c) {
rangeCheckForAdd(index);

Object[] a = c.toArray();
intnumNew = a.length;
ensureCapacityInternal(size+ numNew);// Increments modCount

intnumMoved =size- index;
if(numMoved >0)
System.arraycopy(elementData,index,elementData,index + numNew,
numMoved);

System.arraycopy(a,0,elementData,index,numNew);
size+= numNew;
returnnumNew !=0;
}

区域删除也是类似,先将toIndex后的数据都移动到fromIndex开始的区域上,然后将原toIndex位置以后的数据都置位null,并且重置size的值
protected voidremoveRange(intfromIndex, inttoIndex) {
modCount++;
intnumMoved =size- toIndex;
System.arraycopy(elementData,toIndex,elementData,fromIndex,
numMoved);

// clear to let GC do its work
intnewSize =size- (toIndex-fromIndex);
for(inti = newSize;i <size;i++) {
elementData[i] = null;
}
size= newSize;
}

removeAll和retainAll都是调用的batchRemove方法,只是传入的complement参数不同。在这个方法中会按照size遍历数组,判断集合c是否包含这个元素,如果这个boolean值和传入的complement参数相等(即取交集或取差集),则将这个元素复制到数组的w位置(w初始为0每次复制后加1,即从数组头部开始复制)。在遍历完后finally代码块中再进行操作,注意在这里首先判断了如果try中出错的情况,如果出错了,则r一定不等于size,则将出错的位置以后的所有元素移到数组的w位置开始的区域,并重新计算w。然后再将数组中w后的位置全部置位null,并重新调整size的值。
private booleanbatchRemove(Collection<?> c, boolean complement) {
finalObject[] elementData =this.elementData;
intr =0,w =0;
booleanmodified =false;
try{
for(;r <size;r++)
if(c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
}finally{
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if(r !=size) {
System.arraycopy(elementData,r,
elementData,w,
size- r);
w +=size- r;
}
if(w !=size) {
// clear to let GC do its work
for(inti = w;i <size;i++)
elementData[i] =null;
modCount+=size- w;
size= w;
modified =true;
}
}
returnmodified;
}

iterator方法和listIterator方法分别返回了Itr类和ListItr类。这两个类不多说了,Itr使用了游标cursor来维护当前元素,以实现next等方法。ListItr继承自Itr,但是提供了向前遍历的方法previous

subList方法返回指定范围的一个子list
publicList<E>subList(intfromIndex, inttoIndex) {
subListRangeCheck(fromIndex,toIndex,size);
return newSubList(this,0,fromIndex,toIndex);
}

SubList中维护了父list的引用,并且记录了一个偏移量offset,以便在操作subList时确认索引在父list中的真实索引,例如在SubList的set方法中
publicEset(intindex,Ee) {
rangeCheck(index);
checkForComodification();
EoldValue = ArrayList.this.elementData(offset+ index);
ArrayList.this.elementData[offset+ index] = e;
returnoldValue;
}

Spliterator相关的方法回头一并说。forEach和replaceAll方法都是简单的遍历并调用传入方法。这些不再赘述。

removeIf方法中使用了BitSet类,这个类可以按位存储,非常适合用于存储大数据量的int值。在这里先初始化了具有size个位的BitSet,然后遍历数组,取得满足删除条件filter的索引,然后将BitSet中这个索引位置设置为true,并且记录删除数量。完成后遍历BitSet,每次取得一个False的索引(即不需要删除的元素),将这些不需要删除的元素从数组的头部往后排,处理完BitSet后重新计算size,并且将索引在新的size后的数据全部置位null。
public booleanremoveIf(Predicate<?superE> filter) {
Objects.requireNonNull(filter);
// figure out which elements are to be removed
// any exception thrown from the filter predicate at this stage
// will leave the collection unmodified
intremoveCount =0;
finalBitSet removeSet =newBitSet(size);
final intexpectedModCount =modCount;
final intsize =this.size;
for(inti=0;modCount== expectedModCount && i < size;i++) {
@SuppressWarnings("unchecked")
finalEelement = (E)elementData[i];
if(filter.test(element)) {
removeSet.set(i);
removeCount++;
}
}
if(modCount!= expectedModCount) {
throw newConcurrentModificationException();
}

// shift surviving elements left over the spaces left by removed elements
final booleananyToRemove = removeCount >0;
if(anyToRemove) {
final intnewSize = size - removeCount;
for(inti=0,j=0;(i < size) && (j < newSize);i++,j++) {
i = removeSet.nextClearBit(i);
elementData[j] = elementData[i];
}
for(intk=newSize;k < size;k++) {
elementData[k] = null;// Let gc do its work
}
this.size= newSize;
if(modCount!= expectedModCount) {
throw newConcurrentModificationException();
}
modCount++;
}

returnanyToRemove;
}

sort方法直接调用了Arrays.sort方法,具体的细节以后看Arrays时再研究
public voidsort(Comparator<?superE> c) {
final intexpectedModCount =modCount;
Arrays.sort((E[])elementData,0,size,c);
if(modCount!= expectedModCount) {
throw newConcurrentModificationException();
}
modCount++;
}