JDK中集合包Collection和List的原始碼解讀配合大神的一起看,秒懂。
大神總結的目錄:http://www.cnblogs.com/skywang12345/p/3323085.html(轉載),僅供個人學習,如有抄襲請包容(我也忘了cry....)
一、 總體架構
1) 簡介
Java集合是java提供的工具包,包含了常用的資料結構:集合、連結串列、佇列、棧、陣列、對映等。Java集合工具包位置是java.util.*
Java集合主要可以劃分為4個部分:List列表、Set集合、Map對映、工具類(Iterator迭代器、Enumeration列舉類、Arrays和Collections)、。
2) 架構圖
3) 說明
看上面的框架圖,先抓住它的主幹,即Collection和Map。
1 Collection是一個介面,是高度抽象出來的集合,它包含了集合的基本操作和屬性。
Collection包含了List和Set兩大分支。
(01) List是一個有序的佇列,每一個元素都有它的索引。第一個元素的索引值是0。
List的實現類有LinkedList, ArrayList, Vector, Stack。
(02) Set是一個不允許有重複元素的集合。
Set的實現類有HastSet和TreeSet。HashSet依賴於HashMap,它實際上是通過HashMap實現的;TreeSet依賴於TreeMap,它實際上是通過TreeMap實現的。
2 Map是一個對映介面,即key-value鍵值對。Map中的每一個元素包含“一個key”和“key對應的value”。
AbstractMap是個抽象類,它實現了Map介面中的大部分API。而HashMap,TreeMap,WeakHashMap都是繼承於AbstractMap。
Hashtable雖然繼承於Dictionary,但它實現了Map介面。
接下來,再看Iterator。它是遍歷集合的工具,即我們通常通過Iterator迭代器來遍歷集合。我們說Collection依賴於Iterator,是因為Collection的實現類都要實現iterator()函式,返回一個Iterator物件。
ListIterator是專門為遍歷List而存在的。
再看Enumeration,它是JDK 1.0引入的抽象類。作用和Iterator一樣,也是遍歷集合;但是Enumeration的功能要比Iterator少。在上面的框圖中,Enumeration只能在Hashtable, Vector, Stack中使用。
最後,看Arrays和Collections。它們是運算元組、集合的兩個工具類。
二、 Collection架構
1) 概要
Collection是一個介面,它主要的兩個分支是:List 和Set。
List和Set都是介面,它們繼承於Collection。List是有序的佇列,List中可以有重複的元素;而Set是數學概念中的集合,Set中沒有重複元素!
List和Set都有它們各自的實現類。
為了方便,我們抽象出了AbstractCollection抽象類,它實現了Collection中的絕大部分函式;這樣,在Collection的實現類中,我們就可以通過繼承AbstractCollection省去重複編碼。AbstractList和AbstractSet都繼承於AbstractCollection,具體的List實現類繼承於AbstractList,而Set的實現類則繼承於AbstractSet。
另外,Collection中有一個iterator()函式,它的作用是返回一個Iterator介面。通常,我們通過Iterator迭代器來遍歷集合。ListIterator是List介面所特有的,在List介面中,通過ListIterator()返回一個ListIterator物件。
接下來,我們看看各個介面和抽象類的介紹;然後,再對實現類進行詳細的瞭解。
本章內容包括:
1 Collection簡介
2 List簡介
3 Set簡介
4AbstractCollection
5 AbstractList
6 AbstractSet
7 Iterator
8 ListIterator
2) Collection簡介
Collection的定義如下:
public interfaceCollection<E> extends Iterable<E> {}
它是一個介面,是高度抽象出來的集合,它包含了集合的基本操作:新增、刪除、清空、遍歷(讀取)、是否為空、獲取大小、是否保護某元素等等。
Collection介面的所有子類(直接子類和間接子類)都必須實現2種建構函式:不帶引數的建構函式 和 引數為Collection的建構函式。帶引數的建構函式,可以用來轉換Collection的型別。
// Collection的API
abstractboolean add(E object)
abstractboolean addAll(Collection<?extends E> collection)
abstract void clear()
abstractboolean contains(Object object)
abstractboolean containsAll(Collection<?> collection)
abstractboolean equals(Object object)
abstract int hashCode()
abstractboolean isEmpty()
abstractIterator<E> iterator()
abstractboolean remove(Object object)
abstractboolean removeAll(Collection<?> collection)
abstractboolean retainAll(Collection<?> collection)
abstract int size()
abstract <T>T[] toArray(T[] array)
abstractObject[] toArray()
3) List簡介
List的定義如下:
public interfaceList<E> extends Collection<E> {}
List是一個繼承於Collection的介面,即List是集合中的一種。List是有序的佇列,List中的每一個元素都有一個索引;第一個元素的索引值是0,往後的元素的索引值依次+1。和Set不同,List中允許有重複的元素。
List的官方介紹如下:
A List is acollection which maintains an ordering for its elements. Every element in theList has an index. Each element can thus be accessed by its index, with thefirst index being zero. Normally, Lists allow duplicate elements, as comparedto Sets, where elements have to be unique.
關於API方面。既然List是繼承於Collection介面,它自然就包含了Collection中的全部函式介面;由於List是有序佇列,它也額外的有自己的API介面。主要有“新增、刪除、獲取、修改指定位置的元素”、“獲取List中的子佇列”等。
// List的API
abstractboolean add(E object)
abstractboolean addAll(Collection<?extends E> collection)
abstract void clear()
abstractboolean contains(Object object)
abstractboolean containsAll(Collection<?> collection)
abstractboolean equals(Object object)
abstract int hashCode()
abstractboolean isEmpty()
abstractIterator<E> iterator()
abstractboolean remove(Object object)
abstractboolean removeAll(Collection<?> collection)
abstractboolean retainAll(Collection<?> collection)
abstract int size()
abstract <T>T[] toArray(T[] array)
abstractObject[] toArray()
// 相比與Collection,List新增的API:
abstract void add(int location, E object)
abstractboolean addAll(int location,Collection<? extends E> collection)
abstract E get(int location)
abstract int indexOf(Object object)
abstract int lastIndexOf(Object object)
abstractListIterator<E> listIterator(int location)
abstractListIterator<E> listIterator()
abstract E remove(int location)
abstract E set(int location, E object)
abstractList<E> subList(intstart, int end)
4) Set簡介
Set的定義如下:
public interfaceSet<E> extends Collection<E> {}
Set是一個繼承於Collection的介面,即Set也是集合中的一種。Set是沒有重複元素的集合。
關於API方面。Set的API和Collection完全一樣。
// Set的API
abstractboolean add(E object)
abstractboolean addAll(Collection<?extends E> collection)
abstract void clear()
abstractboolean contains(Object object)
abstractboolean containsAll(Collection<?> collection)
abstractboolean equals(Object object)
abstract int hashCode()
abstractboolean isEmpty()
abstractIterator<E> iterator()
abstract boolean remove(Object object)
abstractboolean removeAll(Collection<?> collection)
abstractboolean retainAll(Collection<?> collection)
abstract int size()
abstract <T>T[] toArray(T[] array)
abstractObject[] toArray()
5) AbstractCollection簡介
AbstractCollection的定義如下:
public abstractclass AbstractCollection<E> implements Collection<E> {}
AbstractCollection是一個抽象類,它實現了Collection中除iterator()和size()之外的函式。
AbstractCollection的主要作用:它實現了Collection介面中的大部分函式。從而方便其它類實現Collection,比如ArrayList、LinkedList等,它們這些類想要實現Collection介面,通過繼承AbstractCollection就已經實現了大部分的介面了。
6) AbstractList簡介
AbstractList的定義如下:
public abstractclass AbstractList<E> extends AbstractCollection<E> implementsList<E> {}
AbstractList是一個繼承於AbstractCollection,並且實現List介面的抽象類。它實現了List中除size()、get(int location)之外的函式。
AbstractList的主要作用:它實現了List介面中的大部分函式。從而方便其它類繼承List。
另外,和AbstractCollection相比,AbstractList抽象類中,實現了iterator()介面。
7) AbstractSet簡介
AbstractSet的定義如下:
public abstractclass AbstractSet<E> extends AbstractCollection<E> implementsSet<E> {}
AbstractSet是一個繼承於AbstractCollection,並且實現Set介面的抽象類。由於Set介面和Collection介面中的API完全一樣,Set也就沒有自己單獨的API。和AbstractCollection一樣,它實現了List中除iterator()和size()之外的函式。
AbstractSet的主要作用:它實現了Set介面中的大部分函式。從而方便其它類實現Set介面。
8) Iterator簡介
Iterator的定義如下:
public interfaceIterator<E> {}
Iterator是一個介面,它是集合的迭代器。集合可以通過Iterator去遍歷集合中的元素。Iterator提供的API介面,包括:是否存在下一個元素、獲取下一個元素、刪除當前元素。
注意:Iterator遍歷Collection時,是fail-fast機制的。即,當某一個執行緒A通過iterator去遍歷某集合的過程中,若該集合的內容被其他執行緒所改變了;那麼執行緒A訪問集合時,就會丟擲ConcurrentModificationException異常,產生fail-fast事件。關於fail-fast的詳細內容,我們會在後面專門進行說明。
// Iterator的API
abstract booleanhasNext()
abstract E next()
abstract void remove()
9) ListIterator簡介
ListIterator的定義如下:
public interfaceListIterator<E> extends Iterator<E> {}
ListIterator是一個繼承於Iterator的介面,它是佇列迭代器。專門用於便利List,能提供向前/向後遍歷。相比於Iterator,它新增了新增、是否存在上一個元素、獲取上一個元素等等API介面。
// ListIterator的API
// 繼承於Iterator的介面
abstract booleanhasNext()
abstract E next()
abstract voidremove()
// 新增API介面
abstract voidadd(E object)
abstract booleanhasPrevious()
abstract intnextIndex()
abstract Eprevious()
abstract intpreviousIndex()
abstract voidset(E object)
三、 ArrayList類
1) ArrayList概要
上一章,我們學習了Collection的架構。這一章開始,我們對Collection的具體實現類進行講解;首先,講解List,而List中ArrayList又最為常用。因此,本章我們講解ArrayList。先對ArrayList有個整體認識,再學習它的原始碼,最後再通過例子來學習如何使用它。內容包括:
第1部分 ArrayList簡介
第3部分 ArrayList原始碼解析(基於JDK1.6.0_45)
第5部分 toArray()異常
第6部分 ArrayList示例
2) ArrayList簡介
ArrayList 是一個數組佇列,相當於 動態陣列。與Java中的陣列相比,它的容量能動態增長。它繼承於AbstractList,實現了List, RandomAccess,Cloneable, java.io.Serializable這些介面。
ArrayList 繼承了AbstractList,實現了List。它是一個數組佇列,提供了相關的新增、刪除、修改、遍歷等功能。
ArrayList 實現了RandmoAccess介面,即提供了隨機訪問功能。RandmoAccess是java中用來被List實現,為List提供快速訪問功能的。在ArrayList中,我們即可以通過元素的序號快速獲取元素物件;這就是快速隨機訪問。稍後,我們會比較List的“快速隨機訪問”和“通過Iterator迭代器訪問”的效率。
ArrayList 實現了Cloneable介面,即覆蓋了函式clone(),能被克隆。
ArrayList 實現java.io.Serializable介面,這意味著ArrayList支援序列化,能通過序列化去傳輸。
和Vector不同,ArrayList中的操作不是執行緒安全的!所以,建議在單執行緒中才使用ArrayList,而在多執行緒中可以選擇Vector或者CopyOnWriteArrayList。
ArrayList建構函式
// 預設建構函式
ArrayList()
// capacity是ArrayList的預設容量大小。當由於增加資料導致容量不足時,容量會新增上一次容量大小的一半。
ArrayList(intcapacity)
// 建立一個包含collection的ArrayList
ArrayList(Collection<?extends E> collection)
ArrayList的API
// Collection中定義的API
boolean add(E object)
boolean addAll(Collection<? extendsE> collection)
void clear()
boolean contains(Object object)
boolean containsAll(Collection<?>collection)
boolean equals(Object object)
int hashCode()
boolean isEmpty()
Iterator<E> iterator()
boolean remove(Object object)
boolean removeAll(Collection<?>collection)
boolean retainAll(Collection<?>collection)
int size()
<T> T[] toArray(T[] array)
Object[] toArray()
//AbstractCollection中定義的API
void add(int location, E object)
boolean addAll(int location,Collection<? extends E> collection)
E get(int location)
int indexOf(Object object)
int lastIndexOf(Object object)
ListIterator<E> listIterator(int location)
ListIterator<E> listIterator()
E remove(int location)
E set(int location, E object)
List<E> subList(int start, int end)
// ArrayList新增的API
Object clone()
void ensureCapacity(int minimumCapacity)
void trimToSize()
void removeRange(int fromIndex, inttoIndex)
3) ArrayList資料結構
ArrayList的繼承關係
java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractList<E>
java.util.ArrayList<E>
public classArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess,Cloneable, java.io.Serializable {}
ArrayList包含了兩個重要的物件:elementData 和 size。
(01) elementData 是"Object[]型別的陣列",它儲存了新增到ArrayList中的元素。實際上,elementData是個動態陣列,我們能通過建構函式 ArrayList(int initialCapacity)來執行它的初始容量為initialCapacity;如果通過不含引數的建構函式ArrayList()來建立ArrayList,則elementData的容量預設是10。elementData陣列的大小會根據ArrayList容量的增長而動態的增長,具體的增長方式,請參考原始碼分析中的ensureCapacity()函式。
(02) size 則是動態陣列的實際大小。
4) ArrayList原始碼解析
為了更瞭解ArrayList的原理,對ArrayList原始碼程式碼作出分析。ArrayList是通過陣列實現的,原始碼比較容易理解。
總結:
(01) ArrayList 實際上是通過一個數組去儲存資料的。當我們構造ArrayList時;若使用預設建構函式,則ArrayList的預設容量大小是10。
(02) 當ArrayList容量不足以容納全部元素時,ArrayList會重新設定容量:新的容量=“(原始容量x3)/2 + 1”。
(03) ArrayList的克隆函式,即是將全部元素克隆到一個數組中。
(04) ArrayList實現java.io.Serializable的方式。當寫入到輸出流時,先寫入“容量”,再依次寫入“每一個元素”;當讀出輸入流時,先讀取“容量”,再依次讀取“每一個元素”。
5) ArrayList遍歷方式
ArrayList支援3種遍歷方式
(01) 第一種,通過迭代器遍歷。即通過Iterator去遍歷。
Integer value =null;
Iterator iter =list.iterator();
while(iter.hasNext()) {
value = (Integer)iter.next();
}
(02) 第二種,隨機訪問,通過索引值去遍歷。
由於ArrayList實現了RandomAccess介面,它支援通過索引值去隨機訪問元素。
Integer value =null;
int size =list.size();
for (int i=0;i<size; i++) {
value = (Integer)list.get(i);
}
(03) 第三種,for迴圈遍歷。如下:
Integer value =null;
for (Integerinteg:list) {
value = integ;
}
遍歷ArrayList時,使用隨機訪問(即,通過索引序號訪問)效率最高,而使用迭代器的效率最低!
System.arraycopy(elementData,0, a, 0, size); // 複製功能
6) toArray()異常
當我們呼叫ArrayList中的toArray(),可能遇到過丟擲“java.lang.ClassCastException”異常的情況。下面我們說說這是怎麼回事。
ArrayList提供了2個toArray()函式:
Object[] toArray()
<T> T[]toArray(T[] contents)
呼叫 toArray() 函式會丟擲“java.lang.ClassCastException”異常,但是呼叫toArray(T[] contents) 能正常返回 T[]。
toArray() 會丟擲異常是因為 toArray() 返回的是Object[] 陣列,將 Object[] 轉換為其它型別(如如,將Object[]轉換為的Integer[])則會丟擲“java.lang.ClassCastException”異常,因為Java不支援向下轉型。具體的可以參考前面ArrayList.java的原始碼介紹部分的toArray()。
解決該問題的辦法是呼叫 <T>T[] toArray(T[] contents) , 而不是 Object[] toArray()。
呼叫 toArray(T[]contents) 返回T[]的可以通過以下幾種方式實現。
// toArray(T[]contents)呼叫方式一
public staticInteger[] vectorToArray1(ArrayList<Integer> v) {
Integer[] newText = new Integer[v.size()];
v.toArray(newText);
return newText;
}
// toArray(T[]contents)呼叫方式二。最常用!
public static Integer[]vectorToArray2(ArrayList<Integer> v) {
Integer[] newText =(Integer[])v.toArray(new Integer[0]);
return newText;
}
// toArray(T[]contents)呼叫方式三
public staticInteger[] vectorToArray3(ArrayList<Integer> v) {
Integer[] newText = new Integer[v.size()];
Integer[] newStrings =(Integer[])v.toArray(newText);
return newStrings;
}
7) ArrayList示例
// 建立ArrayList
ArrayList list =new ArrayList();
// 將“”
list.add("1");
list.add("2");
list.add("3");
list.add("4");
// 將下面的元素新增到第1個位置
list.add(0, "5");
// 獲取第1個元素
System.out.println("thefirst element is: "+ list.get(0));
// 刪除“3”
list.remove("3");
// 獲取ArrayList的大小
System.out.println("Arraylistsize=: "+ list.size());
// 判斷list中是否包含"3"
System.out.println("ArrayListcontains 3 is: "+ list.contains(3));
// 設定第2個元素為10
list.set(1,"10");
// 通過Iterator遍歷ArrayList
for(Iterator iter= list.iterator(); iter.hasNext(); ) {
System.out.println("next is: "+iter.next());
}
// 將ArrayList轉換為陣列
String[] arr =(String[])list.toArray(new String[0]);
for (Stringstr:arr)
System.out.println("str: "+ str);
// 清空ArrayList
list.clear();
// 判斷ArrayList是否為空
System.out.println("ArrayListis empty: "+ list.isEmpty());
四、 fail-fast總結(一種遍歷機制)
1) 概要
我們已經學習了ArrayList。接下來,我們以ArrayList為例,對Iterator的fail-fast機制進行了解。內容包括::
3 fail-fast解決辦法
4 fail-fast原理
5 解決fail-fast的原理
2) fail-fast簡介
fail-fast 機制是java集合(Collection)中的一種錯誤機制。當多個執行緒對同一個集合的內容進行操作時,就可能會產生fail-fast事件。
例如:當某一個執行緒A通過iterator去遍歷某集合的過程中,若該集合的內容被其他執行緒所改變了;那麼執行緒A訪問集合時,就會丟擲ConcurrentModificationException異常,產生fail-fast事件。
在詳細介紹fail-fast機制的原理之前,先通過一個示例來認識fail-fast。
3) fail-fast示例
示例程式碼:(FastFailTest.java)
importjava.util.*;
importjava.util.concurrent.*;
/*
* @desc java集合中Fast-Fail的測試程式。
*
* fast-fail事件產生的條件:當多個執行緒對Collection進行操作時,若其中某一個執行緒通過iterator去遍歷集合時,該集合的內容被其他執行緒所改變;則會丟擲ConcurrentModificationException異常。
* fast-fail解決辦法:通過util.concurrent集合包下的相應類去處理,則不會產生fast-fail事件。
*
* 本例中,分別測試ArrayList和CopyOnWriteArrayList這兩種情況。ArrayList會產生fast-fail事件,而CopyOnWriteArrayList不會產生fast-fail事件。
* (01)使用ArrayList時,會產生fast-fail事件,丟擲ConcurrentModificationException異常;定義如下:
* private static List<String> list = new ArrayList<String>();
* (02)使用時CopyOnWriteArrayList,不會產生fast-fail事件;定義如下:
* private static List<String> list = newCopyOnWriteArrayList<String>();
*
* @author skywang
*/
public classFastFailTest {
private static List<String> list =new ArrayList<String>();
//private static List<String> list =new CopyOnWriteArrayList<String>();
public static void main(String[] args) {
// 同時啟動兩個執行緒對list進行操作!
new ThreadOne().start();
new ThreadTwo().start();
}
private static void printAll() {
System.out.println("");
String value = null;
Iterator iter = list.iterator();
while(iter.hasNext()) {
value = (String)iter.next();
System.out.print(value+",");
}
}
/**
* 向list中依次新增0,1,2,3,4,5,每新增一個數之後,就通過printAll()遍歷整個list
*/
private static class ThreadOne extendsThread {
public void run() {
int i = 0;
while (i<6) {
list.add(String.valueOf(i));
printAll();
i++;
}
}
}
/**
* 向list中依次新增10,11,12,13,14,15,每新增一個數之後,就通過printAll()遍歷整個list
*/
private static class ThreadTwo extendsThread {
public void run() {
int i = 10;
while (i<16) {
list.add(String.valueOf(i));
printAll();
i++;
}
}
}
}
執行結果:
執行該程式碼,丟擲異常java.util.ConcurrentModificationException!即,產生fail-fast事件!
結果說明:
(01) FastFailTest中通過 new ThreadOne().start() 和 new ThreadTwo().start() 同時啟動兩個執行緒去操作list。
ThreadOne執行緒:向list中依次新增0,1,2,3,4,5。每新增一個數之後,就通過printAll()遍歷整個list。
ThreadTwo執行緒:向list中依次新增10,11,12,13,14,15。每新增一個數之後,就通過printAll()遍歷整個list。
(02) 當某一個執行緒遍歷list的過程中,list的內容被另外一個執行緒所改變了;就會丟擲ConcurrentModificationException異常,產生fail-fast事件。
4) fail-fast解決辦法
fail-fast機制,是一種錯誤檢測機制。它只能被用來檢測錯誤,因為JDK並不保證fail-fast機制一定會發生。若在多執行緒環境下使用fail-fast機制的集合,建議使用“java.util.concurrent包下的類”去取代“java.util包下的類”。
所以,本例中只需要將ArrayList替換成java.util.concurrent包下對應的類即可。
即,將程式碼
private staticList<String> list = new ArrayList<String>();
替換為
private staticList<String> list = new CopyOnWriteArrayList<String>();
則可以解決該辦法。
5) fail-fast原理
產生fail-fast事件,是通過丟擲ConcurrentModificationException異常來觸發的。
那麼,ArrayList是如何丟擲ConcurrentModificationException異常的呢?
我們知道,ConcurrentModificationException是在操作Iterator時丟擲的異常。我們先看看Iterator的原始碼。ArrayList的Iterator是在父類AbstractList.java中實現的。程式碼如下:
從中,我們可以發現在呼叫 next() 和 remove()時,都會執行 checkForComodification()。若“modCount 不等於 expectedModCount”,則丟擲ConcurrentModificationException異常,產生fail-fast事件。
要搞明白 fail-fast機制,我們就要需要理解什麼時候“modCount 不等於 expectedModCount”!
從Itr類中,我們知道 expectedModCount 在建立Itr物件時,被賦值為 modCount。通過Itr,我們知道:expectedModCount不可能被修改為不等於 modCount。所以,需要考證的就是modCount何時會被修改。
接下來,我們檢視ArrayList的原始碼,來看看modCount是如何被修改的。
從中,我們發現:無論是add()、remove(),還是clear(),只要涉及到修改集合中的元素個數時,都會改變modCount的值。
接下來,我們再系統的梳理一下fail-fast是怎麼產生的。步驟如下:
(01) 新建了一個ArrayList,名稱為arrayList。
(02) 向arrayList中新增內容。
(03) 新建一個“執行緒a”,並在“執行緒a”中通過Iterator反覆的讀取arrayList的值。
(04) 新建一個“執行緒b”,在“執行緒b”中刪除arrayList中的一個“節點A”。
(05) 這時,就會產生有趣的事件了。
在某一時刻,“執行緒a”建立了arrayList的Iterator。此時“節點A”仍然存在於arrayList中,建立arrayList時,expectedModCount = modCount(假設它們此時的值為N)。
在“執行緒a”在遍歷arrayList過程中的某一時刻,“執行緒b”執行了,並且“執行緒b”刪除了arrayList中的“節點A”。“執行緒b”執行remove()進行刪除操作時,在remove()中執行了“modCount++”,此時modCount變成了N+1!
“執行緒a”接著遍歷,當它執行到next()函式時,呼叫checkForComodification()比較“expectedModCount”和“modCount”的大小;而“expectedModCount=N”,“modCount=N+1”,這樣,便丟擲ConcurrentModificationException異常,產生fail-fast事件。
至此,我們就完全瞭解了fail-fast是如何產生的!
即,當多個執行緒對同一個集合進行操作的時候,某執行緒訪問集合的過程中,該集合的內容被其他執行緒所改變(即其它執行緒通過add、remove、clear等方法,改變了modCount的值);這時,就會丟擲ConcurrentModificationException異常,產生fail-fast事件。
6) 解決fail-fast的原理
上面,說明了“解決fail-fast機制的辦法”,也知道了“fail-fast產生的根本原因”。接下來,我們再進一步談談java.util.concurrent包中是如何解決fail-fast事件的。
還是以和ArrayList對應的CopyOnWriteArrayList進行說明。我們先看看CopyOnWriteArrayList的原始碼:檢視JDK
從中,我們可以看出:
(01) 和ArrayList繼承於AbstractList不同,CopyOnWriteArrayList沒有繼承於AbstractList,它僅僅只是實現了List介面。
(02) ArrayList的iterator()函式返回的Iterator是在AbstractList中實現的;而CopyOnWriteArrayList是自己實現Iterator。
(03) ArrayList的Iterator實現類中呼叫next()時,會“呼叫checkForComodification()比較‘expectedModCount’和‘modCount’的大小”;但是,CopyOnWriteArrayList的Iterator實現類中,沒有所謂的checkForComodification(),更不會丟擲ConcurrentModificationException異常!
五、 LinkedList類
1) 概要
我們已經學習了ArrayList,並瞭解了fail-fast機制。這一章我們接著學習List的實現類——LinkedList。
和學習ArrayList一樣,接下來呢,我們先對LinkedList有個整體認識,然後再學習它的原始碼;最後再通過例項來學會使用LinkedList。內容包括:
第2部分 LinkedList資料結構
第3部分 LinkedList原始碼解析(基於JDK1.6.0_45)
第4部分 LinkedList遍歷方式
第5部分 LinkedList示例
2) LinkedList介紹
LinkedList簡介
LinkedList 是一個繼承於AbstractSequentialList的雙向連結串列。它也可以被當作堆疊、佇列或雙端佇列進行操作。
LinkedList 實現 List 介面,能對它進行佇列操作。
LinkedList 實現 Deque 介面,即能將LinkedList當作雙端佇列使用。
LinkedList 實現了Cloneable介面,即覆蓋了函式clone(),能克隆。
LinkedList 實現java.io.Serializable介面,這意味著LinkedList支援序列化,能通過序列化去傳輸。
LinkedList 是非同步的。
// 預設建構函式
LinkedList()
// 建立一個LinkedList,保護Collection中的全部元素。
LinkedList(Collection<?extends E> collection)
LinkedList的API
boolean add(E object)
void add(int location, E object)
boolean addAll(Collection<? extends E>collection)
boolean addAll(int location, Collection<?extends E> collection)
void addFirst(E object)
void addLast(E object)
void clear()
Object clone()
boolean contains(Object object)
Iterator<E> descendingIterator()
E element()
E get(int location)
E getFirst()
E getLast()
int indexOf(Object object)
int lastIndexOf(Object object)
ListIterator<E> listIterator(int location)
boolean offer(E o)
boolean offerFirst(E e)
boolean offerLast(E e)
E peek()
E peekFirst()
E peekLast()
E poll()
E pollFirst()
E pollLast()
E pop()
void push(E e)
E remove()
E remove(int location)
boolean remove(Object object)
E removeFirst()
boolean removeFirstOccurrence(Object o)
E removeLast()
boolean removeLastOccurrence(Object o)
E set(int location, E object)
int size()
<T> T[] toArray(T[] contents)
Object[] toArray()
AbstractSequentialList簡介
在介紹LinkedList的原始碼之前,先介紹一下AbstractSequentialList。畢竟,LinkedList是AbstractSequentialList的子類。
AbstractSequentialList實現了get(int index)、set(int index, E element)、add(int index, Eelement) 和 remove(int index)這些函式。這些介面都是隨機訪問List的,LinkedList是雙向連結串列;既然它繼承於AbstractSequentialList,就相當於已經實現了“get(intindex)這些介面”。
此外,我們若需要通過AbstractSequentialList自己實現一個列表,只需要擴充套件此類,並提供 listIterator() 和 size() 方法的實現即可。若要實現不可修改的列表,則需要實現列表迭代器的 hasNext、next、hasPrevious、previous 和 index 方法即可。
3) LinkedList資料結構
LinkedList的繼承關係
java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractList<E>
java.util.AbstractSequentialList<E>
java.util.LinkedList<E>
public classLinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>,Cloneable, java.io.Serializable {}
LinkedList的本質是雙向連結串列。
(01) LinkedList繼承於AbstractSequentialList,並且實現了Dequeue介面。
(02) LinkedList包含兩個重要的成員:header 和 size。
header是雙向連結串列的表頭,它是雙向連結串列節點所對應的類Entry的例項。Entry中包含成員變數: previous, next, element。其中,previous是該節點的上一個節點,next是該節點的下一個節點,element是該節點所包含的值。
size是雙向連結串列中節點的個數。
4) LinkedList原始碼解析
為了更瞭解LinkedList的原理,下面對LinkedList原始碼程式碼作出分析。
在閱讀原始碼之前,我們先對LinkedList的整體實現進行大致說明:
LinkedList實際上是通過雙向連結串列去實現的。既然是雙向連結串列,那麼它的順序訪問會非常高效,而隨機訪問效率比較低。
既然LinkedList是通過雙向連結串列的,但是它也實現了List介面{也就是說,它實現了get(int location)、remove(int location)等“根據索引值來獲取、刪除節點的函式”}。LinkedList是如何實現List的這些介面的,如何將“雙向連結串列和索引值聯絡起來的”?
實際原理非常簡單,它就是通過一個計數索引值來實現的。例如,當我們呼叫get(int location)時,首先會比較“location”和“雙向連結串列長度的1/2”;若前者大,則從連結串列頭開始往後查詢,直到location位置;否則,從連結串列末尾開始先前查詢,直到location位置。
這就是“雙線連結串列和索引值聯絡起來”的方法。
好了,接下來開始閱讀原始碼(只要理解雙向連結串列,那麼LinkedList的原始碼很容易理解的)。
總結:
(01) LinkedList 實際上是通過雙向連結串列去實現的。
它包含一個非常重要的內部類:Entry。Entry是雙向連結串列節點所對應的資料結構,它包括的屬性有:當前節點所包含的值,上一個節點,下一個節點。
(02) 從LinkedList的實現方式中可以發現,它不存在LinkedList容量不足的問題。
(03) LinkedList的克隆函式,即是將全部元素克隆到一個新的LinkedList物件中。
(04) LinkedList實現java.io.Serializable。當寫入到輸出流時,先寫入“容量”,再依次寫入“每一個節點保護的值”;當讀出輸入流時,先讀取“容量”,再依次讀取“每一個元素”。
(05) 由於LinkedList實現了Deque,而Deque介面定義了在雙端佇列兩端訪問元素的方法。提供插入、移除和檢查元素的方法。每種方法都存在兩種形式:一種形式在操作失敗時丟擲異常,另一種形式返回一個特殊值(null 或 false,具體取決於操作)。
總結起來如下表格:
第一個元素(頭部) 最後一個元素(尾部)
丟擲異常 特殊值 丟擲異常 特殊值
插入 addFirst(e) offerFirst(e) addLast(e) offerLast(e)
移除 removeFirst() pollFirst() removeLast() pollLast()
檢查 getFirst() peekFirst() getLast() peekLast()
(06) LinkedList可以作為FIFO(先進先出)的佇列,作為FIFO的佇列時,下表的方法等價:
佇列方法 等效方法
add(e) addLast(e)
offer(e) offerLast(e)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()
(07) LinkedList可以作為LIFO(後進先出)的棧,作為LIFO的棧時,下表的方法等價:
棧方法 等效方法
push(e) addFirst(e)
pop() removeFirst()
peek() peekFirst()
5) LinkedList遍歷方式
LinkedList支援多種遍歷方式。建議不要採用隨機訪問的方式去遍歷LinkedList,而採用逐個遍歷的方式。
(01) 第一種,通過迭代器遍歷。即通過Iterator去遍歷。
(02) 通過快速隨機訪問遍歷LinkedList
int size =list.size();
for (int i=0;i<size; i++) {
list.get(i);
}
(03) 通過另外一種for迴圈來遍歷LinkedList
for (Integerinteg:list);
(04) 通過pollFirst()來遍歷LinkedList
while(list.pollFirst()!= null);
(05) 通過pollLast()來遍歷LinkedList
while(list.pollLast()!= null);
(06) 通過removeFirst()來遍歷LinkedList
try {
while(list.removeFirst() != null)
;
} catch(NoSuchElementException e) {
}
(07) 通過removeLast()來遍歷LinkedList
try {
while(list.removeLast() != null)
;
} catch(NoSuchElementException e) {}
測試這些遍歷方式效率如下:
執行結果:
iteratorLinkedListThruIterator:8 ms
iteratorLinkedListThruForeach:3724 ms
iteratorThroughFor2:5 ms
iteratorThroughPollFirst:8 ms
iteratorThroughPollLast:6 ms
iteratorThroughRemoveFirst:2 ms
iteratorThroughRemoveLast:2 ms
由此可見,遍歷LinkedList時,使用removeFist()或removeLast()效率最高。但用它們遍歷時,會刪除原始資料;若單純只讀取,而不刪除,應該使用第3種遍歷方式。
無論如何,千萬不要通過隨機訪問去遍歷LinkedList!
6) LinkedList示例
importjava.util.List;
importjava.util.Iterator;
importjava.util.LinkedList;
importjava.util.NoSuchElementException;
/*
* @desc LinkedList測試程式。
*
* @author skywang
* @email [email protected]
*/
public class LinkedListTest{
public static void main(String[] args) {
// 測試LinkedList的API
testLinkedListAPIs() ;
// 將LinkedList當作 LIFO(後進先出)的堆疊
useLinkedListAsLIFO();
// 將LinkedList當作 FIFO(先進先出)的佇列
useLinkedListAsFIFO();
}
/*
* 測試LinkedList中部分API
*/
private static void testLinkedListAPIs() {
String val = null;
//LinkedList llist;
//llist.offer("10");
// 新建一個LinkedList
LinkedList llist = new LinkedList();
//---- 新增操作 ----
// 依次新增1,2,3
llist.add("1");
llist.add("2");
llist.add("3");
// 將“4”新增到第一個位置
llist.add(1, "4");
System.out.println("\nTest\"addFirst(), removeFirst(), getFirst()\"");
// (01) 將“10”新增到第一個位置。 失敗的話,丟擲異常!
llist.addFirst("10");
System.out.println("llist:"+llist);
// (02) 將第一個元素刪除。 失敗的話,丟擲異常!
System.out.println("llist.removeFirst():"+llist.removeFirst());
System.out.println("llist:"+llist);
// (03) 獲取第一個元素。 失敗的話,丟擲異常!
System.out.println("llist.getFirst():"+llist.getFirst());
System.out.println("\nTest\"offerFirst(), pollFirst(), peekFirst()\"");
// (01) 將“10”新增到第一個位置。 返回true。
llist.offerFirst("10");
System.out.println("llist:"+llist);
// (02) 將第一個元素刪除。 失敗的話,返回null。
System.out.println("llist.pollFirst():"+llist.pollFirst());
System.out.println("llist:"+llist);
// (03) 獲取第一個元素。 失敗的話,返回null。
System.out.println("llist.peekFirst():"+llist.peekFirst());
System.out.println("\nTest\"addLast(), removeLast(), getLast()\"");
// (01) 將“20”新增到最後一個位置。 失敗的話,丟擲異常!
llist.addLast("20");
System.out.println("llist:"+llist);
// (02) 將最後一個元素刪除。 失敗的話,丟擲異常!
System.out.println("llist.removeLast():"+llist.removeLast());
System.out.println("llist:"+llist);
// (03) 獲取最後一個元素。 失敗的話,丟擲異常!
System.out.println("llist.getLast():"+llist.getLast());
System.out.println("\nTest\"offerLast(), pollLast(), peekLast()\"");
// (01) 將“20”新增到第一個位置。 返回true。
llist.offerLast("20");
System.out.println("llist:"+llist);
// (02) 將第一個元素刪除。 失敗的話,返回null。
System.out.println("llist.pollLast():"+llist.pollLast());
System.out.println("llist:"+llist);
// (03) 獲取第一個元素。 失敗的話,返回null。
System.out.println("llist.peekLast():"+llist.peekLast());
// 將第3個元素設定300。不建議在LinkedList中使用此操作,因為效率低!
llist.set(2, "300");
// 獲取第3個元素。不建議在LinkedList中使用此操作,因為效率低!
System.out.println("\nget(3):"+llist.get(2));
// ---- toArray(T[] a) ----
// 將LinkedList轉行為陣列
String[] arr =(String[])llist.toArray(new String[0]);
for (String str:arr)
System.out.println("str:"+str);
// 輸出大小
System.out.println("size:"+llist.size());
// 清空LinkedList
llist.clear();
// 判斷LinkedList是否為空
System.out.println("isEmpty():"+llist.isEmpty()+"\n");
}
/**
* 將LinkedList當作 LIFO(後進先出)的堆疊
*/
private static void useLinkedListAsLIFO() {
System.out.println("\nuseLinkedListAsLIFO");
// 新建一個LinkedList
LinkedList stack = new LinkedList();
// 將1,2,3,4新增到堆疊中
stack.push("1");
stack.push("2");
stack.push("3");
stack.push("4");
// 列印“棧”
System.out.println("stack:"+stack);
// 刪除“棧頂元素”
System.out.println("stack.pop():"+stack.pop());
// 取出“棧頂元素”
System.out.println("stack.peek():"+stack.peek());
// 列印“棧”
System.out.println("stack:"+stack);
}
/**
* 將LinkedList當作 FIFO(先進先出)的佇列
*/
private static void useLinkedListAsFIFO() {
System.out.println("\nuseLinkedListAsFIFO");
// 新建一個LinkedList
LinkedList queue = new LinkedList();
// 將10,20,30,40新增到佇列。每次都是插入到末尾
queue.add("10");
queue.add("20");
queue.add("30");
queue.add("40");
// 列印“佇列”
System.out.println("queue:"+queue);
// 刪除(佇列的第一個元素)
System.out.println("queue.remove():"+queue.remove());
// 讀取(佇列的第一個元素)
System.out.println("queue.element():"+queue.element());
// 列印“佇列”
System.out.println("queue:"+queue);
}
}
六、 Vector類
1) 概要
學完ArrayList和LinkedList之後,我們接著學習Vector。學習方式還是和之前一樣,先對Vector有個整體認識,然後再學習它的原始碼;最後再通過例項來學會使用它。
第1部分 Vector介紹
第2部分 Vector資料結構
第3部分 Vector原始碼解析(基於JDK1.6.0_45)
第4部分 Vector遍歷方式
第5部分 Vector示例
2) Vector介紹
Vector共有4個建構函式
// 預設建構函式
Vector()
// capacity是Vector的預設容量大小。當由於增加資料導致容量增加時,每次容量會增加一倍。
Vector(intcapacity)
// capacity是Vector的預設容量大小,capacityIncrement是每次Vector容量增加時的增量值。
Vector(intcapacity, int capacityIncrement)
// 建立一個包含collection的Vector
Vector(Collection<?extends E> collection)
Vector的API
synchronizedboolean add(E object)
void add(int location, E object)
synchronizedboolean addAll(Collection<?extends E> collection)
synchronizedboolean addAll(int location,Collection<? extends E> collection)
synchronizedvoid addElement(E object)
synchronizedint capacity()
void clear()
synchronizedObject clone()
boolean contains(Object object)
synchronizedboolean containsAll(Collection<?> collection)
synchronizedvoid copyInto(Object[] elements)
synchronizedE elementAt(int location)
Enumeration<E> elements()
synchronizedvoid ensureCapacity(intminimumCapacity)
synchronizedboolean equals(Object object)
synchronizedE firstElement()
E get(int location)
synchronizedint hashCode()
synchronizedint indexOf(Object object, intlocation)
int indexOf(Object object)
synchronizedvoid insertElementAt(E object,int location)
synchronizedboolean isEmpty()
synchronizedE lastElement()
synchronizedint lastIndexOf(Object object,int location)
synchronizedint lastIndexOf(Object object)
synchronized E remove(int location)
boolean remove(Object object)
synchronizedboolean removeAll(Collection<?> collection)
synchronizedvoid removeAllElements()
synchronizedboolean removeElement(Objectobject)
synchronizedvoid removeElementAt(intlocation)
synchronizedboolean retainAll(Collection<?> collection)
synchronizedE set(int location, Eobject)
synchronizedvoid setElementAt(E object, intlocation)
synchronized void setSize(int length)
synchronizedint size()
synchronizedList<E> subList(int start,int end)
synchronized<T> T[] toArray(T[]contents)
synchronizedObject[] toArray()
synchronizedString toString()
synchronizedvoid trimToSize()
3) Vector資料結構
Vector的繼承關係
java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractList<E>
java.util.Vector<E>
public classVector<E>
extends AbstractList<E>
implements List<E>, RandomAccess,Cloneable, java.io.Serializable {}
Vector的資料結構和ArrayList差不多,它包含了3個成員變數:elementData , elementCount,capacityIncrement。
(01) elementData 是"Object[]型別的陣列",它儲存了新增到Vector中的元素。elementData是個動態陣列,如果初始化Vector時,沒指定動態陣列的>大小,則使用預設大小10。隨著Vector中元素的增加,Vector的容量也會動態增長,capacityIncrement是與容量增長相關的增長係數,具體的增長方式,請參考原始碼分析中的ensureCapacity()函式。
(02) elementCount 是動態陣列的實際大小。
(03)capacityIncrement 是動態陣列的增長係數。如果在建立Vector時,指定了capacityIncrement的大小;則,每次當Vector中動態陣列容量增加時>,增加的大小都是capacityIncrement。
4) Vector原始碼解析
為了更瞭解Vector的原理,下面對Vector原始碼程式碼作出分析。
總結:
(01) Vector實際上是通過一個數組去儲存資料的。當我們構造Vecotr時;若使用預設建構函式,則Vector的預設容量大小是10。
(02) 當Vector容量不足以容納全部元素時,Vector的容量會增加。若容量增加係數 >0,則將容量的值增加“容量增加係數”;否則,將容量大小增加一倍。
(03) Vector的克隆函式,即是將全部元素克隆到一個數組中。
5) Vector遍歷方式
Vector支援4種遍歷方式。建議使用下面的第二種去遍歷Vector,因為效率問題。
(01) 第一種,通過迭代器遍歷。即通過Iterator去遍歷。
Integer value =null;
for(Iterator iter= vec.iterator(); vec.hasNext();) value = (Integer)iter.next();
(02) 第二種,隨機訪問,通過索引值去遍歷。
由於Vector實現了RandomAccess介面,它支援通過索引值去隨機訪問元素。
Integer value =null;
int size =vec.size();
for (int i=0;i<size; i++) {
value = (Integer)vec.get(i);
}
(03) 第三種,另一種for迴圈。如下:
Integer value =null;
for (Integerinteg:vec) {
value = integ;
}
(04) 第四種,Enumeration遍歷。如下:
Integer value =null;
Enumeration enu = vec.elements();
while(enu.hasMoreElements()) {
value = (Integer)enu.nextElement();
}
測試這些遍歷方式效率的如下:
執行結果:
iteratorThroughRandomAccess:6 ms
iteratorThroughIterator:9 ms
iteratorThroughFor2:8 ms
iteratorThroughEnumeration:7 ms
總結:遍歷Vector,使用索引的隨機訪問方式最快,使用迭代器最慢。
6) Vector示例
importjava.util.Vector;
importjava.util.List;
importjava.util.Iterator;
importjava.util.Enumeration;
/**
* @desc Vector測試函式:遍歷Vector和常用API
*
* @author skywang
*/
public classVectorTest {
public static void main(String[] args) {
// 新建Vector
Vector vec = new Vector();
// 新增元素
vec.add("1");
vec.add("2");
vec.add("3");
vec.add("4");
vec.add("5");
// 設定第一個元素為100
vec.set(0, "100");
// 將“500”插入到第3個位置
vec.add(2, "300");
System.out.println("vec:"+vec);
// (順序查詢)獲取100的索引
System.out.println("vec.indexOf(100):"+vec.indexOf("100"));
// (倒序查詢)獲取100的索引
System.out.println("vec.lastIndexOf(100):"+vec.lastIndexOf("100"));
// 獲取第一個元素
System.out.println("vec.firstElement():"+vec.firstElement());
// 獲取第3個元素
System.out.println("vec.elementAt(2):"+vec.elementAt(2));
// 獲取最後一個元素
System.out.println("vec.lastElement():"+vec.lastElement());
// 獲取Vector的大小
System.out.println("size:"+vec.size());
// 獲取Vector的總的容量
System.out.println("capacity:"+vec.capacity());
// 獲取vector的“第2”到“第4”個元素
System.out.println("vec 2 to4:"+vec.subList(1, 4));
// 通過Enumeration遍歷Vector
Enumeration enu = vec.elements();
while(enu.hasMoreElements())
System.out.println("nextElement():"+enu.nextElement());
Vector retainVec = new Vector();
retainVec.add("100");
retainVec.add("300");
// 獲取“vec”中包含在“retainVec中的元素”的集合
System.out.println("vec.retain():"+vec.retainAll(retainVec));
System.out.println("vec:"+vec);
// 獲取vec對應的String陣列
String[] arr = (String[])vec.toArray(new String[0]);
for (String str:arr)
System.out.println("str:"+str);
// 清空Vector。clear()和removeAllElements()一樣!
vec.clear();
// vec.removeAllElements();
// 判斷Vector是否為空
System.out.println("vec.isEmpty():"+vec.isEmpty());
}
}
七、 stack類
1) 概要
學完Vector了之後,接下來我們開始學習Stack。Stack很簡單,它繼承於Vector。學習方式還是和之前一樣,先對Stack有個整體認識,然後再學習它的原始碼;最後再通過例項來學會使用它。內容包括:
第1部分 Stack介紹
第2部分 Stack原始碼解析(基於JDK1.6.0_45)
第3部分 Stack示例
2) Stack介紹
Stack簡介
Stack是棧。它的特性是:先進後出(FILO, First In Last Out)。
java工具包中的Stack是繼承於Vector(向量佇列)的,由於Vector是通過陣列實現的,這就意味著,Stack也是通過陣列實現的,而非連結串列。當然,我們也可以將LinkedList當作棧來使用!在“Java 集合系列06之 Vector詳細介紹(原始碼解析)和使用示例”中,已經詳細介紹過Vector的資料結構,這裡就不再對Stack的資料結構進行說明了。
Stack的繼承關係
java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractList<E>
java.util.Vector<E>
java.util.Stack<E>
public classStack<E> extends Vector<E> {}
Stack的建構函式
Stack只有一個預設建構函式,如下:
Stack()
Stack的API
Stack是棧,它常用的API如下:
boolean empty()
synchronizedE peek()
synchronizedE pop()
E push(E object)
synchronizedint search(Object o)
由於Stack和繼承於Vector,因此它也包含Vector中的全部API。
3) Stack原始碼解析
Stack的原始碼非常簡單,下面我們對它進行學習。
總結:
(01) Stack實際上也是通過陣列去實現的。
執行push時(即,將元素推入棧中),是通過將元素追加的陣列的末尾中。
執行peek時(即,取出棧頂元素,不執行刪除),是返回陣列末尾的元素。
執行pop時(即,取出棧頂元素,並將該元素從棧中刪除),是取出陣列末尾的元素,然後將該元素從陣列中刪除。
(02) Stack繼承於Vector,意味著Vector擁有的屬性和功能,Stack都擁有。
4) Vector示例
importjava.util.Stack;
importjava.util.Iterator;
importjava.util.List;
/**
* @desc Stack的測試程式。測試常用API的用法
*
* @author skywang
*/
public classStackTest {
public static void main(String[] args) {
Stack stack = new Stack();
// 將1,2,3,4,5新增到棧中
for(int i=1; i<6; i++) {
stack.push(String.valueOf(i));
}
// 遍歷並打印出該棧
iteratorThroughRandomAccess(stack) ;
// 查詢“2”在棧中的位置,並輸出
int pos = stack.search("2");
System.out.println("the postion of2 is:"+pos);
// pop棧頂元素之後,遍歷棧
stack.pop();