1、揭開ArrayList真面目
作者將在本文詳細贅述日常開發中最常用集合類-ArrayList,本次JCF源碼分析基于JDK1.7,主要從以下幾個方向分析:
- UML類圖關系
- 數據結構
- 接口介紹
- 常用、重要方法的實現
1.1 UML類圖關系
(
UML類圖
)
從UML關系類圖,我們可以直觀的看出ArrayList的類結構,圖中虛線表示實現(implements
)關系,實線表示繼承(extends
)關系,我們不必在還不熟悉的情況下對這種關系進行“死記硬背”,因為在理解整個集合框架之后自然不用看都會比較清晰,辣么下面我們將對接口或類一一介紹:
1.2 數據結構
1.2.1 JAVA常用數據結構
在介紹Collection具體的實現類或者某一種抽象之前,筆者需要對JAVA最常用的數據結構進行簡單贅述
- 線性結構:線性結構的元素在物理內存上有序/連續的分布,使其可以使用下標(
Index
)快速訪問,查找復雜度為O(1),如:數組 - 鏈表結構:鏈表結構的元素在物理內存上無序/隨機的分布,當前元素保留上一個或下一個元素的引用叫單向鏈表,保留上一個且下一個元素的引用叫雙向鏈表 ,鏈表中第一個元素的上一個索引位置指向最后一個元素且最后一個元素的下一個索引位置指向第一個元素叫循環鏈表(
簡單理解成一根繩子頭尾相連成圓
),查詢復雜度為O(logn),如:LinkedList、LinkedHashMap - 散列結構:
以上是筆者覺得最常見的一些數據結構,筆者會在后續介紹Collection不同抽象的時候講解集合每一種API對應的數據結構是哪一種,理解這些數據結構的結構及優缺點有助于讀者在根據具體的場景下選用是和數據結構的API提高性能,當然JDK里面還有一些跳表、樹、圖的數據結構實現,因為文章是介紹常用的集合類,所以就不一一介紹了,如果讀者有興趣可以自行學習
1.3 接口介紹
1.3.1 接口的語義
在這之前筆者希望讀者對接口的理解可以上升到一個邏輯層次,而不是僅僅知道實現了接口就必須實現接口的方法,比如Iterable接口是可迭代的語義,那么子類就必須是具備可迭代需求才能實現該接口,否則就屬于一種錯誤的設計,再看看JDK中沒有任何方法的Serializable
、以及上圖中出現的RandomAccess
這些接口的出現本身就是約束著一種邏輯定義,筆者在這里稱之為接口的語義
1.3.2 Iterable接口
集合頂層接口,接口的語義標志實現類是可迭代的(循環
),所有需要被for(String str : strs)
語法迭代的支持都必須實現此接口(數組除外
),接口方法就一個Iterator<T> iterator();
此方法返回一個迭代器(Iterator接口
)的實現類。
1.3.3 Collection接口
幾乎是JAVA所有數據結構集合的接口(二元散列數據結構除外,如:Map
),接口的語義標志實現類必須為集合,集合的解釋也就是:“一堆東西”。集合里的“東西”,叫作元素(element
),既然是一堆東西而我們目的是要對這堆東西進行操作,自然Collection的接口就需要約束每個集合都必須有以下方法:
-
size()
: 集合中元素的個數 -
contains(Object element)
: 集合中是否包含某個元素 -
iterator()
: 集合的迭代器 -
add(Object element)
:往集合里添加元素 -
remove(Object elemnt)
:從集合里面移除元素
理論上一個簡單的集合接口有以上方法就可以正常工作,但是為了操作方便JCF作者Josh Bloch增加了以下幾個方法的約束:
-
isEmpty()
:集合是否為空 -
toArray()
:轉換成數組 -
containsAll(Collection collection)
:集合是否包含collection變量中的所有元素 -
addAll(Collection collection)
:添加collection中所有元素到當前集合 -
removeAll(Collection collection)
:移除所有在collection中的元素 -
retainAll(Collection collection)
:取兩個集合的交集 -
clear()
:清除集合所有元素
有了以上方法的約束可以讓我們在使用集合的時候操作更方便
1.3.4 List接口
介紹了常用數據結構,接下來我們來看Collection的第一種集合抽象——有序List列表,List有以上兩種數據結構的實現: 線性結構(ArrayList
)、鏈表結構(LinkedList
),本文我們將贅述線性結構的實現(ArrayList
),List接口在extends了Collection之后,增加了對有序列表操作的幾個方法約束:
-
get(int i)
:獲取i位置的元素 -
set(int i,Object element)
:替換集合中的i位置插入element元素,返回原位置的元素 -
add(int i,Object element)
:集合i位置插入element,i位置以后的元素向后移一位 -
remove(int i)
:移除集合中i位置的元素 -
indexOf(Object element)
:查找element并返回元素在集合的下標索引,如果沒有返回-1 -
lastIndexOf(Object element)
:查找element元素最后一次在集合中的下標索引,如果沒有返回-1 -
listIterator()
:創建一個集合的迭代器 -
listIterator(int i)
:從i位置創建一個List集合的迭代器 -
subList(int star,int end)
:返回當前集合的一個子集,從當前集合的start位置到end位置,注意此子集并不是新new出來的對象,所以對子集的操作會影響到當前集合。
以上方法都是針對有序集合的操作,根據有序集合的特性約束一系列依靠下標索引(Index
)的操作
1.3.5 RandomAccess接口
前面提到過該接口,此接口沒有任何方法,存在的意義僅僅是為了表示邏輯上的一種語義——實現了該接口的子類應該具有一個特性:表明其支持快速(通常是固定復雜度)隨機訪問。此接口的主要目的是允許一般的算法(二分法
、快速排序
...)更改其行為,從而在將其應用到隨機或連續訪問列表時能提供良好的性能,所以遵循了該接口語義的實現類使用for(int i=0;i<xx;i++)
會比使用Iterator
迭代器速度要快,如:ArrayList根據下標索引直接訪問任何位置的元素。
1.3.6 Serializable接口
此接口沒有任何方法,存在的意義僅僅是為了表示邏輯上的一種語義——實現類可被序列化的。
1.3.7 Cloneable接口
此接口沒有任何方法,存在的意義僅僅是為了表示邏輯上的一種語義——實現類可被克隆的。
1.4 常用、重要方法的實現
介紹完接口之后,我們來看下UML類圖中AbstrctCollection
、AbstractList
、ArrayList
的具體實現類,在講述過程中筆者會省去那些非重點的方法,比如toString()
、isEmpty()
等方法。
1.4.1 AbstractCollection抽象類
作為Collection的抽象類及幾乎所有集合類的父類來說(筆者說的是幾乎,比如Map就非其子類
),理應實現所有Collection接口中約束的最基本方法,但有些約束是要在具體場景下才能根據具體需求進行實現,如:add()
需要把對象添加到具體的數據結構中,可以是數組(線性
)、Node(鏈表
),所以 AbstractCollection中主要實現了以下方法:
contains(Object var1)方法實現
/**
* 方法作用:集合中是否包含var1元素
* 實現方式:使用迭代器進行迭代比對,如果先相同返回
*/
public boolean contains(Object var1) {
Iterator var2 = this.iterator();//抽象方法,具體在子類中會實現
if(var1 == null) {
while(var2.hasNext()) {
if(var2.next() == null) {
return true;
}
}
} else {
while(var2.hasNext()) {
if(var1.equals(var2.next())) {
return true;
}
}
}
return false;
}
toArray()方法實現
/**
* 方法作用:集合轉成數組
* 實現方式:new一個集合大小一致的數組,然后用集合迭代器迭代復制到新數組,這個地方的復制
* 使用的Arrays.copy()方法,這個方法在整個線性結構的集合里面大量存在,幾乎所
* 所有的集合修改操作都使用到了它,而這個方法底層的實現來自于一個JNI方法,
* System.arrayCopy(),由于是本地方法所以效率非常高
*/
public Object[] toArray() {
Object[] var1 = new Object[this.size()];
Iterator var2 = this.iterator();
for(int var3 = 0; var3 < var1.length; ++var3) {
if(!var2.hasNext()) {
return Arrays.copyOf(var1, var3);//線性結構的集合中大量使用的方法
}
var1[var3] = var2.next();
}
/* finishToArray(var1,va2)方法是當new的這個數組大小不足集合大小時完成多余的賦值
* 那么,明明是根據集合size new出來的數組為什么會放不下呢,因為很可能在復制的過程
* 中集合被修改了。
*/
return var2.hasNext()?finishToArray(var1, var2):var1;
}
add(E var1)方法實現,前面說過了不同子類有不同的實現
public boolean add(E var1) {
throw new UnsupportedOperationException();
}
remove(Object var1)方法實現
/**
* 方法作用:從集合中移除var1元素
* 實現方式:獲取迭代器,使用迭代器中的remove刪除元素
*/
public boolean remove(Object var1) {
Iterator var2 = this.iterator();//抽象方法,具體在子類中會實現
if(var1 == null) {
while(var2.hasNext()) {
if(var2.next() == null) {
var2.remove();//調用迭代器的remove
return true;
}
}
} else {
while(var2.hasNext()) {
if(var1.equals(var2.next())) {
var2.remove();
return true;
}
}
}
return false;
}
containsAll(Collections<?> var1);
/**
* 方法作用:集合中是否包含所有var1集合中的元素
* 實現方式:寫法可以借鑒,如果是你會這樣寫嗎?是不是代碼整潔一些呢
*/
public boolean containsAll(Collection<?> var1) {
Iterator var2 = var1.iterator();
Object var3;
do {
if(!var2.hasNext()) {
return true;
}
var3 = var2.next();
} while(this.contains(var3));//調用上面的contains()方法,所以是兩層循環
return false;
}
addAll(Collection<? extends E> var1)方法實現
/**
* 方法作用:把var1集合中的所有數據添加到集合
* 實現方式:沒什么好說的~~
*/
public boolean addAll(Collection<? extends E> var1) {
boolean var2 = false;
Iterator var3 = var1.iterator();
while(var3.hasNext()) {
Object var4 = var3.next();
if(this.add(var4)) { //調用上面的add()方法,實際是子類中實現的add()方法
var2 = true;
}
}
return var2;
}
retainAll(Collection<?> var1)
/**
* 方法作用:刪除當前集合在var1集合中存在的元素(集合交集,但是會把原集合數據從內存刪除)
* 實現方式:代碼很好懂,不多說,只要刪除過一個元素 返回True,否則False
*/
public boolean retainAll(Collection<?> var1) {
boolean var2 = false;
Iterator var3 = this.iterator();
while(var3.hasNext()) {
if(!var1.contains(var3.next())) {
var3.remove();
var2 = true;
}
}
return var2;
}
AbstractCollection總結:雖然抽象類中實現了很多方法,但實際還是基于抽象方法之上的,具體的業務邏輯都在子類實現的抽象方法中,而AbstractCollection中實現的是與業務沒關系的公共代碼,或者說最原始、最基本的實現,比如:所有查找都是使用的迭代器,實際上我們之前介紹過RandomAccess接口的語義,所以遵循了該接口語義的實現類使用
for(int i=0;i<xx;i++)
會比使用Iterator
迭代器速度要快,后面我們會看到ArrayList對contains()
方法進行了重寫
1.4.2 AbstractList抽象類
indexOf(Object o)方法實現
/**
* 方法作用:查找元素o在集合中的位置,沒有則返回-1
* 實現方式:利用迭代器進行查詢
*/
public int indexOf(Object o) {
ListIterator<E> it = listIterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return it.previousIndex();
} else {
while (it.hasNext())
if (o.equals(it.next()))
return it.previousIndex();
}
return -1;
}
iterator()方法實現,生成按從前到后順序迭代的迭代器
public Iterator<E> iterator() {
return new AbstractList.Itr();//生成迭代器,即下面的迭代器內部類實現代碼
}
/**
* 迭代器內部類實現,此迭代器只有next()獲取下一個元素的方法,所以迭代的順序是從前到后
*/
private class Itr implements Iterator<E> {
int cursor; //下一個元素索引
int lastRet; //調用next()方法返回對象的下標索引
int expectedModCount; //模數:集合每次的修改都會+1,用來判斷在迭代過程中集合是否修改過,
//下面會會詳細介紹
private Itr() {
this.cursor = 0; //初始化迭代器的時候下一個元素索引從0開始
this.lastRet = -1;//默認-1
this.expectedModCount = AbstractList.this.modCount;//賦值為集合的模數
}
public boolean hasNext() {
//如果當前索引 != 集合大小 說名還有下一個
return this.cursor != AbstractList.this.size();
}
public E next() {
//獲取下一個的時候,校驗迭代器模數是否==集合模數,如果不等于則證明集合在迭代器
//生成之后被修改過
this.checkForComodification();
try {
int var1 = this.cursor;
Object var2 = AbstractList.this.get(var1);
this.lastRet = var1;//var2對象的下標索引
this.cursor = var1 + 1; //這部分代碼很好理解,返回下一個 當前索引+1
return var2;
} catch (IndexOutOfBoundsException var3) {
this.checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if(this.lastRet < 0) {
throw new IllegalStateException();
} else {
//校驗迭代器模數是否==集合模數,如果不等于就說明集合在迭代器生成之后被修改過
this.checkForComodification();
try {
AbstractList.this.remove(this.lastRet);//remove當前index的元素
if(this.lastRet < this.cursor) {
--this.cursor; //刪除成功后,下一個元素為-1
}
//重置-1
this.lastRet = -1;
//因為做了修改集合大小的操作,所以重新給模數賦值
this.expectedModCount = AbstractList.this.modCount;
} catch (IndexOutOfBoundsException var2) {
throw new ConcurrentModificationException();
}
}
}
/**
* 校驗迭代器模數是否==集合模數,如果不等于則證明集合在迭代器生成之后被修改過
* 如果集合被修改過,說名迭代器失效,拋出異常
*/
final void checkForComodification() {
if(AbstractList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
listIterator()方法實現
public ListIterator<E> listIterator(int var1) {
//校驗索引var1是否在0到集合size以內,否則IndexOutOfBoundsException
this.rangeCheckForAdd(var1);
return new AbstractList.ListItr(var1);
}
/**
* 迭代器內部類實現,迭代順序隨意的迭代器,并且在迭代過程中可以修改集合
*
*/
private class ListItr extends AbstractList.Itr implements ListIterator<E> {
ListItr(int var2) {
super();
this.cursor = var2;
}
public boolean hasPrevious() {
return this.cursor != 0;//當下一個索引 !=0 的時候說名還有上一個
}
public E previous() { //返回上一個元素
this.checkForComodification();
try {
int var1 = this.cursor - 1;
Object var2 = AbstractList.this.get(var1);
this.lastRet = this.cursor = var1;
return var2;
} catch (IndexOutOfBoundsException var3) {
this.checkForComodification();
throw new NoSuchElementException();
}
}
public int nextIndex() {
return this.cursor;//返回下一個元素的索引
}
public int previousIndex() {
return this.cursor - 1;//返回上一個元素的索引
}
public void set(E var1) {
if(this.lastRet < 0) {
throw new IllegalStateException();
} else {
this.checkForComodification();
try {
AbstractList.this.set(this.lastRet, var1);//把元素var1替換當前位置的值
this.expectedModCount = AbstractList.this.modCount;
} catch (IndexOutOfBoundsException var3) {
throw new ConcurrentModificationException();
}
}
}
public void add(E var1) {
this.checkForComodification();
try {
int var2 = this.cursor;
AbstractList.this.add(var2, var1);//添加元素,并且把模數更新
this.lastRet = -1;
this.cursor = var2 + 1;
this.expectedModCount = AbstractList.this.modCount;
} catch (IndexOutOfBoundsException var3) {
throw new ConcurrentModificationException();
}
}
}
AbstractList總結:主要實現了List接口中的以上3個方法,注意此處
indexOf()
仍然使用的是迭代器遍歷的,因為List的數據結構分為線性和鏈表結構,下面我們可以看到線性結構的ArrayList實現RandomAccess
接口后對indexOf()
進行了重寫
- Itr迭代器實現
只能向后迭代,并且在迭代過程中不能對集合元素進行add、set操作,但可以remove
- ListItr迭代器實現
1.ListIterator和Iterator都有hasNext()和next()方法,可以實現順序向后遍歷,但是ListIterator有hasPrevious()和previous()方法,可以實現逆向(順序向前)遍歷。Iterator不可以。
2.ListIterator可以定位當前索引的位置,nextIndex()和previousIndex()可以實現。Iterator沒有此功能。
3.都可實現刪除操作,但是ListIterator可以實現對象的修改,set()方法可以實現修改,add()可以實現添加,Iterator僅能遍歷,不能修改。
1.4.3 ArrayList實現類
private int size;//集合里總共包含多少個元素,應該 <= elementData.length;
private transient Object[] elementData; //這行源碼表示ArrayList底層為數組實現,那么既然底層是數組,怎么實現擴容的集合呢?請看下面這個grow()
方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = 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);//前面提到讓讀者留意的工具類
}
/* 再看看Arrays.copy()方法實現,其實是System.arraycopy方法,這個方法是本地方法,效率會比較快 */
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));//底層System.arraycopy()實現
return copy;
}
前面說過ArrayList會對contains()方法重寫,接下來我們詳細看下
public boolean contains(Object o) {
return indexOf(o) >= 0;//調用的indexOf()方法,我們還記得這個方法在AbstractList中
//使用迭代器實現過,但是ArrayList又對其重寫了
}
/* ArrayList已經使用for(int i=0;i<size;i++)來實現, 回想下前面我們說過的
* RandomAccess接口的語義就明白這個的用意,不記得的童鞋可以回到上面看下
*/
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
get()方法實現
public E get(int index) {
rangeCheck(index);//范圍校驗,不在0到size內就IndexOutOfBoundsException
return elementData(index);//直接通過數組下標返回值
}
set()方法實現
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);//拿出index位置的元素
elementData[index] = element;//設置index位置的元素為新elment
return oldValue;//返回原index位置元素
}
add()方法實現
public boolean add(E e) {
ensureCapacityInternal(size + 1);//如果elementData數組長度不夠當前size+1,就擴容
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {//如果elementData還是0,則取minCapacity和默認長度10兩個數中的大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //因為是添加,所以模數需要加1
if (minCapacity - elementData.length > 0)
grow(minCapacity);//前面介紹過,如果長度不夠則擴容
}
remove()方法實現
public E remove(int index) {
rangeCheck(index);
modCount++; //模數自增,筆者反復強調模數,因為這是一種防止線程安全的措施
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);//本地方法copy數組,把index后的所有數組往前挪一個位置
//最后一個位置置為空,注意看很多JDK源碼中都會寫到 Help GC或者下面這段話,
//因為設置引用為null之后,GC可以檢查到GC Roots 不可達,從而回收內存
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
subList(int fromIndex, int toIndex)方法實現
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);//校驗fromIndex和toIndex是否合法
return new SubList(this, 0, fromIndex, toIndex);
}
/* 產生一個當前集合從fromIndex位置到toIndex位置的子集合,
* 這個地方大家看源碼留意下子集合的產生過程?
*/
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;//保存父集合的引用
private final int parentOffset;//上面的fromIndex
private final int offset;//針對父集合的偏移量,后續根據用戶輸入的index進行偏移
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;//默認賦值為fromIndex
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
public E get(int index) {
rangeCheck(index);
checkForComodification();
//其實就是獲取父集合指定位置的元素,所以子集合并沒有new出新內存,這點請讀者理解
return ArrayList.this.elementData(offset + index);
}
public int size() {
checkForComodification();
return this.size;
}
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
protected void removeRange(int fromIndex, int toIndex) {
checkForComodification();
parent.removeRange(parentOffset + fromIndex,
parentOffset + toIndex);
this.modCount = parent.modCount;
this.size -= toIndex - fromIndex;
}
public boolean addAll(Collection<? extends E> c) {
return addAll(this.size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
int cSize = c.size();
if (cSize==0)
return false;
checkForComodification();
parent.addAll(parentOffset + index, c);
this.modCount = parent.modCount;
this.size += cSize;
return true;
}
ArrayList總結:因為ArrayList的底層是數組實現,所以它是一個線性的結構,它在內存中是一段連續的地址,所以我們可以通過Index很快的訪問元素,然而我們看到了每add或者remove操作都會調用System.arraycopu()復制改動之后的所有元素,所以我們說隨機插入和刪除操作線性結構沒有鏈表結構效率高(這里請大家注意,后續講到鏈表結構的時候會做一個對比,看看是否一定線性結構的效率比較高)
然后請大家注意的是subList()方法,上面有說過subList并非產生一個新對象,而是通過使用下標偏移量取父類數組中的元素,也就是說子集合和父集合其實在內存中是一個集合,所以對子集合的任何修改將直接影響到父集合,我們很多剛入門的童鞋,可能再使用中會遇到這個問題,所以在這里提出來
總結
ArrayList講到這里就已經講完了,因為ArrayList作為我們JCF框架源碼分析系列
的第一集,所以里面著重的介紹了Iterable、Iterator、Collection、AbstractCollection、List、Abstract、RandomAccess等接口或抽象類,后續文章中如有重復的就不在贅述。