轉載請註明出處(請尊重原創!謝謝~):
http://blog.csdn.net/javazejian/article/details/53073995
出自【zejian的博客】
關聯文章:
java數據結構與算法之順序表與鏈表設計與實現分析
java數據結構與算法之雙鏈表設計與實現
java數據結構與算法之改良順序表與雙鏈表類似ArrayList和LinkedList(帶Iterator迭代器與fast-fail機制)
這篇是數據結構與算法的第3篇,通過前兩篇的介紹,對應順序表和鏈表已有比較深入的瞭解,而本篇是前兩篇的延續,即優化前面所分析過的順序表和雙向鏈表(帶頭結點和尾結點,均不帶數據)。以下是主要的知識點:
[TOC]
理解Iterator接口
爲什麼需要迭代器(Iterator)
在分析迭代器前,我們先來了解一下爲什麼需要迭代器,在以前的代碼中,我們總是通過如下去循環獲取某些集合中的元素:
List<String> list = new ArrayList<String>();
for(int i = 0 ; i < list.size() ; i++){
String str = list.get(i);
//do something....
}
或者是
List<String> list = new LinkedList<String>();
for(int i = 0 ; i < list.size() ; i++){
String str = list.get(i);
//do something....
}
事實上以上的方式是完全沒有問題的,但是大家有沒想過爲什麼可以這樣去獲取值呢?其實很大原因是我們已瞭解了ArrayList和LinkedList集合的內部結構,因此我們知道可以通過list.get(i)可以獲取到元素也可以通過list.remove()可以刪除元素,什麼意思?現在來假設一個集合(不屬於Collection和Map),該集合是SquenceHashColl,請遍歷該集合元素,此時大家會如何做呢,有人會說很簡單啊,去查該集合的API啊,是的,沒錯,那去查API不就相當於去了解其結構了麼?難道就沒有其他方法可以在不查看API或瞭解內部結構的情況下去遍歷SquenceHashColl集合的嗎?到此先來屢屢問題出現的原因,然後再給出解決方案,上述在遍歷SquenceHashColl集合時,之所以無法在不知道API或內部結構的時候獲取元素,主要原因是訪問代碼和集合本身是緊密耦合的,無法將訪問邏輯從集合類和客戶端代碼中分離出來,也就是說我們沒有辦法遍歷集合的原因是訪問集合元素的邏輯與集合本身耦合度太高了,從而必須在瞭解其API或者內部結構的基礎上才能遍歷該集合,那麼現在只要把訪問邏輯從集合本身的代碼剝離出來問題也就迎刃而解了,但該如何剝離兩者呢?這時迭代器就出場了,有了迭代器訪問集合元素的邏輯和集合本身就很容易分離開來了,問題迭代器是什麼啊?客官,別急,且聽,慢慢道來…..
迭代器(Iterator)的分析
關於迭代器,在這裏我們可以簡單地理解爲遍歷,是一個標準化遍歷各類容器裏面的所有對象的方法類,它是本身也是一種典型的設計模式。Iterator 模式是用於遍歷集合類的標準訪問方法。它可以把訪問邏輯從不同類型的集合類中抽象出來,從而避免向客戶端暴露集合的內部結構。由於迭代器總是用同一種邏輯來遍歷集合,因此只要該集合內部實現了迭代器,就可以在不知道API或者集合內部結構的情況下通過迭代器遍歷該集合的元素。下面我們先來對比一下使用迭代器和不使用迭代器的遍歷方法:
List<String> list = new LinkedList<String>();
//不使用迭代器
for(int i = 0 ; i < list.size() ; i++){
String str = list.get(i);
//do something......
}
//使用迭代器
//獲取該集合的迭代器
Iterator iterator = list.iterator();
//判斷是否有下一個元素
while(iterator.hasNext()){
//獲取迭代器的值
String string = iterator.next();
//do something......
}
//SquenceHashColl集合(虛構的集合,假設該集合內部實現Iterator)
SquenceHashColl shc=new SquenceHashColl();
//獲取該集合的迭代器
Iterator iterator = shc.iterator();
//判斷是否有下一個元素
while(iterator.hasNext()){
//獲取迭代器的值
String string = iterator.next();
//do something......
}
由上述代碼可見,使用迭代器(Iterator)可以在不知道集合API和內部結構的情況下很容易的獲取集合的元素。接下來我們引入Java JDK中爲我們提供的Iterator接口,它提供了迭代了基本規則,在Java JDK 中他是這樣定義的:對 collection 進行迭代的迭代器。其接口定義如下:
public interface Iterator<E> {
/**
* 判斷是否還有下一個元素
*/
boolean hasNext();
/**
* 返回元素的值
*/
E next();
/**
* 移除元素
*/
default void remove() {
throw new UnsupportedOperationException("remove");
}
/**
* 這是Java8爲Iterator新增的默認方法,該方法可使用lambda表達式來遍歷集合元素。
* 本篇我們先不分析該方法
* @since 1.8
*/
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
在大多數情況下,我們一般只需使用 next()、hasNext() 兩個方法即可完成元素迭代。
迭代器(Iterator)的簡單實現
ok~,在理解了迭代器後,我們來看看在順序表中迭代器的實現(爲了避免與Java中的ArrayList名字衝突,這裏順序表就命名爲MyArrayList,關於MyArrayList的get方法和remove方法的實現與前兩篇的實現基本一樣,Itr爲迭代器,是順序表MyArrayList的內部類):
**
* 迭代器-Itr
* Blog : http://blog.csdn.net/javazejian
*/
private class Itr implements Iterator<T> {
/**
* 表示將要訪問的下一個元素的下標
* index of next element to return
*/
int cursor;
/**
* 當前正在訪問的元素下標,如果沒有則返回-1
* index of last element returned; -1 if no such
*/
int lastRet = -1;
/**
* 先判斷是否還有下一個元素
* @return
*/
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public T next() {
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
//獲取順序表中的數組集合,然後操作該數組元素
Object[] elementData = MyArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;//加一,移動到下一個要訪問的下標
return (T) elementData[lastRet = i];
}
/**
* 使用迭代器的方法移除元素
*/
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
try {
//移除當前操作的元素
MyArrayList.this.remove(lastRet);
//修改當前下標指向
cursor = lastRet;
//復原
lastRet = -1;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
從代碼可以知道,cursor表示還未被訪問的下一個元素下標,lastRet表示的是正在被訪問的元素下標,hasNext()方法實現中可知,通過判斷還未被訪問的下一個元素的下標來判斷是否還有可訪問元素。
public boolean hasNext() {
return cursor != size;
}
而在next()方法實現中,可看出在執行next時,cursor可訪問的下標元素會賦值i變量,最終i會賦值給lastRet變量,此時lastRet下標就代表着正在被訪問元素的下標,而cursor則執行加1操作,繼而指向下一個未被訪問的元素(此時並不知道還有沒下一個元素,需要通過hasNext()方法判斷),還有一點需要特別注意的是,在next方法中使用的元素數組是來自順序表MyArrayList.this.elementData,實際上迭代器是順序表MyArrayList的內部類Itr。
@SuppressWarnings("unchecked")
public T next() {
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
//獲取當前集合
Object[] elementData = MyArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;//加一,移動到下一個要訪問的下標
return (T) elementData[lastRet = i];
}
在remove方法中,通過在next()方法訪問時記錄的當前正在訪問的元素下標lastRet,然後調用順序表的MyArrayList.this.remove(lastRet)移除元素,此時由於移除了元素,lastRet所代表下標的元素即爲刪除元素後移動到該位置的新元素,因此該元素是未被訪問的元素即修改cursor的值爲lastRet,同時lastRet的值復原爲-1。
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
try {
//移除當前操作的元素
MyArrayList.this.remove(lastRet);
//修改當前下標指向
cursor = lastRet;
//復原
lastRet = -1;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
到此迭代器的3個方法分析完成,可以小結出以下幾點:
1、迭代器Iterator內部最終操作的也是順序表MyArrayList的數組和方法
2、hasNext()通過cursor判斷將要被訪問的元素是否存在
3、next()返回的正在被訪問的元素,同時會保存該元素的下標指向lastRet,而cursor會指向下一個未被訪問的元素。
4、remove()移除後數組元素需要移動,同時cursor指向需要更新爲lastRet,因爲lastRet此時代表着往前移動的元素。移除過程可以簡單理解爲下圖:
順序表有了迭代器,我們就可以通過以下方式訪問順序表MyArrayList的元素了,簡易代碼如下:
MyArrayList<Integer> myArrayList=new MyArrayList<>();
myArrayList.add(2);
myArrayList.add(10);
myArrayList.add(1);
myArrayList.add(9);
//通過迭代器方法
Iterator iterator=myArrayList.iterator();
while (iterator.hasNext()){
System.out.println("iterator.next->"+iterator.next());
}
迭代器(Iterator)與集合間存在的問題
雖然上述過程中我們已在順序表MyArrayList集合中實現了迭代器,但是MyArrayList集合與迭代器間卻還存在着一個嚴重的問題,那就是數據一致性,爲什麼會出現這個問題呢?現在假設我們通過myArrayList的迭代器遍歷元素,此時會調用next方法,其內部會進行一個操作:
//獲取當前MyArrayList的數組集合
Object[] elementData = MyArrayList.this.elementData;
迭代器會先獲取整個數組對象,然後進行取值操作,但是在使用iterator的同時,假設我們又去操作調用了myArrayList本身的方法比如MyArrayList.remove(index),那麼此時elementData數組的元素就會發生變化,而迭代器此時剛好也通過了hasNext方法判斷,正在調用next方法,但最後獲取到的值卻已被之前修改過了,不是期望值,從而也就造成了數據不一致的問題。舉個簡單的例子如下,假如集合中存放了10號、20號、30號、40號的球衣,我們可以通過兩種方式取出球衣,一種是通過MyArrayList集合本身的方法remove()(在迭代過程中調用,迭代器並不知情),另一種則是通過迭代器本身的remove方法,過程如下所示(這裏我們簡單把remove理解爲獲取球衣):
MyArrayList<Integer> myArrayList=new MyArrayList<>();
myArrayList.add(10);
myArrayList.add(20);
myArrayList.add(30);
myArrayList.add(40);
System.out.println("-------------iterator--------------");
//迭代前獲取到的集合有4件球衣
boolean flag=true;
Iterator<Integer> it = myArrayList.iterator();
while (it.hasNext()) {
//調用集合myArrayList的remove
//在迭代器不知情的情況下移除了20,剩餘3個球衣
if(flag){
flag=false;
myArrayList.remove(new Integer(20));
}
//結果只拿到3件球衣
it.remove();//迭代器本身的方法
}
從程序中可以看出,迭代器想從集合中獲取4件球衣,實際上獲取迭代器it時,集合中確實是有4件球衣的,但是在通過it.hasNext()判斷後,卻調用了myArrayList.remove(new Integer(20))拿走了20號球衣,注意此時迭代器並不知情,最後的結果就是迭代器壓根兒沒有拿到20號球衣,在迭代過程20號球衣不翼而飛,連聲通知都沒有,這顯然是不合理的啊,要拿走至少也得通知一聲吧,說好的人與人之間的信任呢?淡然無存,悲涼吶。。這還是單線程呢,那麼多線程問題就更明顯了,是吧?現在來屢屢問題在哪裏,顯然是myArrayList用自身的remove方法拿走了一件球衣卻沒有通知迭代器(Iterator),如果有通知,那麼迭代器執行相應的策略也就合理了,那現在的問題是該如何通知迭代器呢?要解決這個問題,我們得看到最本質的問題,移除後(remove),最顯著的變化是啥?沒錯,MyArrayList.this.elementData的存儲元素實際個數發生變化了,也可理解爲結構發生變化了(因爲元素被移除了),也就意味着我們必須在結構發生變化時告訴迭代器,”myArrayList:‘迭代器啊,我拿了一個元素了,你自己注意一下啊’;Iterator:‘好的,我正在遍歷呢,我改變一下策略。’”,這樣的話使用起來也就合情合理。那麼這個策略到底是啥呢?其實事情的經過是這樣的,在MyArrayList我們可以用一個變量,比如modCount=0來記錄結構變化的次數,當添加元素或刪除元素或清空集合時,讓modCount++,記錄變化,那麼每次我們在獲取迭代器時,把當前記錄的modCount變化值賦予給內部類Iterator的一個變量比如expectedModCount=modCount,然後每次在操作it.next()或者it.remove()就去判斷expectedModCount與modCount是否相等即可,只要不相等就說明集合結構已發生變化,這時我們便不再迭代元素並執行對應的邏輯即可。簡易代碼過程如下:
/**
* Created by zejian on 2016/11/8.
* Blog : http://blog.csdn.net/javazejian [請尊重原創,轉載註明出處]
* 改良的順序表類似java集合類ArrayList
*/
public class MyArrayList<T> implements java.io.Serializable,IList<T>,Iterable<T>{
private static final long serialVersionUID = 8683452581122892389L;
/**
* 默認大小
*/
private static final int DEFAULT_CAPACITY = 10;
private int size;
/**
* 記錄修改次數,適用於快速失敗機制
*/
private int modCount;
/**
* 存儲數據的數組
*/
private T[] elementData;
//......省略其他代碼
/**
* 擴容的方法
* @param capacity
*/
public void ensureCapacity(int capacity) {
//如果需要拓展的容量比現在數組的容量還小,則無需擴容
if (capacity<size)
return;
modCount++;//<-------------------------------記錄元素變化
.......
}
/**
* 清空數據
*/
@Override
public void clear() {
modCount++;//<-------------------------------記錄元素變化
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
/**
* 添加
* Blog : http://blog.csdn.net/javazejian
* @param index
* @param data
*/
@Override
public void add(int index, T data) {
........省略其他代碼
size++;
//記錄變化
modCount++;//<-------------------------------記錄元素變化
}
/**
* 根據下標移除元素
* Blog : http://blog.csdn.net/javazejian
* @param index
* @return
*/
@Override
public T remove(int index) {
rangeCheck(index);
modCount++;//<-------------------------------記錄元素變化
.......省略其他代碼
}
/**
* 返回迭代器
* @return
*/
@Override
public Iterator<T> iterator() {
return new Itr();
}
/**
* 迭代器-Itr
* Blog : http://blog.csdn.net/javazejian
*/
private class Itr implements Iterator<T> {
/**
* 表示將要訪問的下一個元素的下標
* index of next element to return
*/
int cursor;
/**
* 當前正在訪問的元素下標,如果沒有則返回-1
* index of last element returned; -1 if no such
*/
int lastRet = -1;
/**
* 修改標識符,用於判斷集合是否被修改
*/
int expectedModCount = modCount;//<-------------------初始化修改標識符
/**
* 先判斷是否還有下一個元素
* @return
*/
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public T next() {
//檢測集合是否已被修改
checkForComodification();//<---------------------------調用前檢測集合是否已被修改
....... 省略其他代碼
}
/**
* 使用迭代器的方法移除元素
*/
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
//檢測集合是否已被改變
checkForComodification();//<---------------------------調用前檢測集合是否已被修改
//........省略其他代碼
}
/**
* 檢測modCount標識符
*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();//<-------修改則拋出異常
}
}
上述省略大部分代碼(完整源碼最終會通過GitHub提供給大家),此時我們關注點是MyArrayList本身的modCount和Iterator的expectedModCount,通過這兩個變量我們就可以實現上述描述的策略,可以看到在添加刪除或者情況元素時都會使modCount++來記錄集合結構修改次數,在獲取迭代器時,會把modCount賦值給Iterator.expectedModCount變量,此時modCount與expectedModCount肯定是相等,在Iterator迭代元素的過程中如果MyArrayList調用自身方法使集合結構發生變化,那麼modCount肯定會變化即modCount與expectedModCount肯定會不相等,也就是說在Iterator迭代的過程中只要檢測到modCount!=expectedModCount,則說明結構發生了變化也就沒必要繼續迭代元素了,此時便ConcurrentModificationException異常,終止迭代操作。其實該策略就是Java JDK的常用集合中的快速失敗機制(fast-fail機制),接下來我們就來重新整理一下前面的問題,然後深入理解Java JDK中的快速失敗機制。
理解快速失敗機制(fast-fail機制)
通過上述的分析,我們已經大概瞭解瞭解快速失敗機制的執行原理。現在就來重新瞭解fail-fast機制,“快速失敗”即fail-fast,它是 Java 集合的一種錯誤檢測機制。當多個線程對集合進行結構上的改變的操作或者集合在迭代元素時直接直接調用自身方法改變集合結構而沒有通知迭代器時,有可能會觸發 fail-fast 機制並拋出ConcurrentModificationException異常。這裏需要特別注意的是,有可能觸發 fail-fast 機制而不是肯定。觸發原理是在迭代過程中,集合的結構發生了變化而此時迭代器並不知道或者還沒來得及反應時便會產生fail-fast機制。這裏我們再次強調,迭代器的快速失敗行爲無法得到保證,因爲一般來說,不可能對是否出現不同步併發修改或者自身修改做出任何硬性保證。快速失敗迭代器會盡最大努力拋出 ConcurrentModificationException。因此,爲提高這類迭代器的正確性而編寫一個依賴於此異常的程序是錯誤的做法,因爲迭代器的快速失敗行爲應該僅用於檢測 bug而已。爲了更好的理解fail-fast機制,在這裏我們使用Java JDK提供的ArrayList作爲測試對象(當然我們自己實現的MyArrayList和MyLinkedList也實現的fail-fast機制,但爲了更有說服力在此還是使用官方的ArrayList)。
/**
* Created by zejian on 2016/11/20.
* Blog : http://blog.csdn.net/javazejian [請尊重原創,轉載註明出處]
*/
public class FailFastTest {
public static void main(String[] args){
ArrayList<Integer> list =new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);
Iterator iterator=list.iterator();
while (iterator.hasNext()){
list.remove(1);//移除第2個元素
int value= (int) iterator.next();
System.out.println(value);
}
}
}
迭代過程移除調用list自身方法移除元素,運行結構如下:
該異常是不是很熟悉,相信不少人在操作集合過程中都遇到過。那麼該異常如何解決呢?實際上最根本的原因使我們調用了list自身的remove方法,只要我們調用Iterator的remove方法,那麼問題就迎刃而解了,如下:
/**
* Created by zejian on 2016/11/20.
* Blog : http://blog.csdn.net/javazejian [請尊重原創,轉載註明出處]
*/
public class FailFastTest {
public static void main(String[] args){
ArrayList<Integer> list =new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);
Iterator iterator=list.iterator();
while (iterator.hasNext()){
Integer value= (Integer) iterator.next();
if(value==20){
iterator.remove();//調用iterator的方法移除第2個元素
}else {
System.out.println("value-->"+value);
}
}
}
}
輸出結果如下:
此時我們可能有點迷糊了,爲什麼iterator的remove就沒有問題呢,其實看一下源碼就明白了,Iterator的remove方法源碼如下:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
//檢測modCount與expectedModCount是否相等
checkForComodification();
try {
//注意本身還是調用ArrayList.this.remove()
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
//但是!!!expectedModCount重新賦值了!
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
現在明白了吧。雖然Iterator的remove方法調用了ArrayList的remove方法(此時modCount++),但同時也對expectedModCount 重新賦值了 ,所以迭代器和ArrayList的結構變化達到一致協調(可以簡單理解爲迭代器接收到了ArrayList的通知),因此不會觸發fast-fail。當然,還有一種情況,那就是多線程操作,比如存在兩個線程(線程 A、線程 B),線程 A通過 Iterator 在遍歷集合 list 中的元素,在某個時候線程 B修改了集合 list 的結構(注意是結構上面的修改,而不是修改集合元素的內容),那麼這個時候也很可能會觸發 fail-fast 機制並拋出 ConcurrentModificationException 異常,但是解決該場景的方案是使用CopyOnWriteArrayList (線程安全的集合類)來替換 ArrayList,但此知識點不在本篇討論範圍就不過多解釋了。到此相信大家對fast-fail機制已有比較深入的理解了,切記fast-fail機制只會儘可能的拋出異常。稍後我們改良的順序表MyArraryList和鏈表MyLinkedList也會實現fail-fast機制。
進化版的ListIterator接口
在開始實現MyArraryList和MyLinkedList之前,我們最後來簡單瞭解一下ListIterator接口,有了前面的Iterator接口,那爲什麼還需要ListIterator呢?其實ListIterator可以說是Iterator的升級版接口,因爲在Iterator中指向向後迭代元素和刪除元素,而ListIterator的出現使得迭代過程添加元素也成爲可能,同時還可以向前遍歷元素,下面我們簡單看一下ListIterator接口方法即可:
public interface ListIterator<E> extends Iterator<E> {
/**
* 是否還有下一個元素即cursor指向的下一元素
* @return
*/
boolean hasNext();
/**
* 獲取元素值
* @return
*/
E next();
/**
* 是否有前驅元素
* @return
*/
boolean hasPrevious();
/**
* 獲取上一個元素的值
* @return
*/
E previous();
/**
* 下一個將要訪問元素的下標
* @return
*/
int nextIndex();
/**
* 上一個將要訪問元素的下標
* @return
*/
int previousIndex();
/**
* 刪除元素
*/
void remove();
/**
* 替換元素的值
* @param e
*/
void set(E e);
/**
* 添加元素
* @param e
*/
void add(E e);
}
ok~,關於ListIterator我們就先了解到這。
改良的MyArraryList的實現
前面終於鋪墊完了,現在開始改良我們的順序表,不過在前面我們已聊得差不多了,改良後的順序表添加了Iterator和ListIterator迭代器,同時新增了fast-fail機制,初始化MyArraryList時默認大小爲10等,下面我們直接給出源碼吧,實現比較簡單,有點類似Java中的ArrayList集合,如果我們搞明白了MyArraryList後看JDK的ArrayList集合源碼是相當輕鬆的事。
package com.zejian.structures.LinkedList.MyCollection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;
/**
* Created by zejian on 2016/11/8.
* Blog : http://blog.csdn.net/javazejian [請尊重原創,轉載註明出處]
* 改良的順序表類似java集合類ArrayList
*/
public class MyArrayList<T> implements java.io.Serializable,IList<T>,Iterable<T>{
private static final long serialVersionUID = 8683452581122892389L;
/**
* 默認大小
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空值數組
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
private int size;
/**
* 記錄修改次數,適用於快速失敗機制
*/
private int modCount;
/**
* 存儲數據的數組
*/
private T[] elementData;
public MyArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = (T[]) new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = (T[]) EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public MyArrayList() {
this.elementData = (T[]) new Object[DEFAULT_CAPACITY];
}
/**
* 擴容的方法
* @param capacity
*/
public void ensureCapacity(int capacity) {
//如果需要拓展的容量比現在數組的容量還小,則無需擴容
if (capacity<size)
return;
modCount++;//記錄元素變化
T[] old = elementData;
elementData = (T[]) new Object[capacity];
//複製元素
for (int i=0; i<size() ; i++)
elementData[i]=old[i];
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size()==0;
}
/**
* 清空數據
*/
@Override
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
@Override
public T get(int index) {
//檢測下標
rangeCheck(index);
return elementData[index];
}
@Override
public T set(int index, T data) {
//檢測下標
rangeCheck(index);
T old=elementData[index];
elementData[index]=data;
return old;
}
@Override
public boolean add(T data) {
add(size(),data);
return true;
}
/**
* 添加
* Blog : http://blog.csdn.net/javazejian
* @param index
* @param data
*/
@Override
public void add(int index, T data) {
//判斷容量是否充足
if(elementData.length==size())
ensureCapacity(size()*2+1);//擴容
//根據index找到需要插入的位置
for (int i=size; i>index; i--)
elementData[i]=elementData[i-1];
//賦值
elementData[index]=data;
size++;
//記錄變化
modCount++;
}
/**
* 根據data查詢下標
* Blog : http://blog.csdn.net/javazejian
* @param data
* @return
*/
@Override
public int indexOf(T data) {
if (data == null) {
//查找null的下標
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//查找有數據的下標
for (int i = 0; i < size; i++)
if (data.equals(elementData[i]))
return i;
}
return -1;
}
/**
* 根據data查找最後一個的index
* Blog : http://blog.csdn.net/javazejian
* @param data
* @return
*/
@Override
public int lastIndexOf(T data) {
//倒序查找即可
if (data == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (data.equals(elementData[i]))
return i;
}
return -1;
}
@Override
public boolean remove(T data) {
if (data == null) {
throw new NullPointerException("data can\'t be empty");
} else {
for (int index = 0; index < size; index++)
if (data.equals(elementData[index])) {
this.remove(indexOf(data));
return true;
}
}
return false;
}
/**
* 根據下標移除元素
* Blog : http://blog.csdn.net/javazejian
* @param index
* @return
*/
@Override
public T remove(int index) {
rangeCheck(index);
modCount++;
T oldValue = elementData[index];
for (int i=index;i<size()-1;i++){
elementData[i]=elementData[i+1];
}
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
/**
* 檢測下標
* @param index
*/
private void rangeCheck(int index) {
if (index<0||index >= size)
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
}
@Override
public boolean contains(T data) {
return indexOf(data) >= 0;
}
/**
* 提供從指定index開始遍歷的迭代器
* Blog : http://blog.csdn.net/javazejian
* @param index
* @return
*/
public ListIterator<T> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
/**
* 提供從0開始遍歷的迭代器
* Blog : http://blog.csdn.net/javazejian
* @return
*/
public ListIterator<T> listIterator() {
return new ListItr(0);
}
/**
* 返回迭代器
* @return
*/
@Override
public Iterator<T> iterator() {
return new Itr();
}
/**
* 迭代器-Itr
* Blog : http://blog.csdn.net/javazejian
*/
private class Itr implements Iterator<T> {
/**
* 表示將要訪問的下一個元素的下標
* index of next element to return
*/
int cursor;
/**
* 當前正在訪問的元素下標,如果沒有則返回-1
* index of last element returned; -1 if no such
*/
int lastRet = -1;
/**
* 修改標識符,用於判斷集合是否被修改
*/
int expectedModCount = modCount;
/**
* 先判斷是否還有下一個元素
* @return
*/
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public T next() {
//檢測集合是否已被修改
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
//獲取當前集合
Object[] elementData = MyArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;//加一,移動到下一個要訪問的下標
return (T) elementData[lastRet = i];
}
/**
* 使用迭代器的方法移除元素
*/
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
//檢測集合是否已被改變
checkForComodification();
try {
//移除當前操作的元素
MyArrayList.this.remove(lastRet);
//修改當前下標指向
cursor = lastRet;
//復原
lastRet = -1;
//更新標識符,防止拋出異常
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
/**
* 檢測modCount標識符
*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
/**
* Blog : http://blog.csdn.net/javazejian
* 可以前移指向的迭代器-ListItr
*/
private class ListItr extends Itr implements ListIterator<T> {
ListItr(int index) {
super();
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
//獲取當前正在被遍歷的元素值
@SuppressWarnings("unchecked")
public T previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = MyArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (T) elementData[lastRet = i];
}
public void set(T e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
MyArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//遍歷過程可用於添加元素
public void add(T data) {
checkForComodification();
try {
int i = cursor;
MyArrayList.this.add(i, data);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
//測試
public static void main(String[] args){
MyArrayList<Integer> myArrayList=new MyArrayList<>();
myArrayList.add(2);
myArrayList.add(10);
myArrayList.add(1);
myArrayList.add(9);
print(myArrayList);
System.out.println("-------------");
myArrayList.remove(2);
print(myArrayList);
System.out.println("-------------");
System.out.println("index-->"+myArrayList.indexOf(10));
myArrayList.set(0,0);
print(myArrayList);
System.out.println("-------------iterator--------------");
Iterator iterator=myArrayList.iterator();
while (iterator.hasNext()){
System.out.println("iterator.next-->"+iterator.next());
}
System.out.println("-------------foreach--------------");
for(Integer data : myArrayList){
System.out.println("data-->"+data);
}
}
public static void print(MyArrayList myArrayList){
for (int i=0;i<myArrayList.size();i++) {
System.out.println("i->"+myArrayList.get(i));
}
}
}
改良的MyLinkedList的實現
關於MyLinkedList,我們主要進行了以下的改良,聲明全局size,不再通過遍歷鏈表的方式去獲取size的大小,而在增加或者減少結點的同時更新size,因此size()方法的操作時間爲O(1)。
@Override
public int size() {
return size;
}
同時爲不區分插入刪除位置操作情況,MyLinkedList中新建了兩個沒有帶數據的結點,分別爲頭結點first和尾結點last,聲明如下:
/**
* 頭部指向結點,不帶數據,排除特殊情況,優化代碼量
*/
private Node<T> first;
/**
* 尾部指向結點,不帶數據,排除特殊情況,優化代碼量
*/
private Node<T> last;
/**
* 初始化鏈表
*/
public MylinkeList() {
first=new Node<>(null,null,null);
last=new Node<>(first,null,null);
first.next=last;
size=0;
modCount++;//記錄修改次數
}
結構如下:
接着我們對結點的查找,採用了二分查找的方式,根據index傳入值分爲從頭結點往後查找和尾結點往前查找,使查詢效率更高效代碼如下:
/**
* 優化結點查詢,根據情況而定查詢起點
* Blog : http://blog.csdn.net/javazejian
* @param index
* @return
*/
Node<T> getNode(int index) {
//如果index小於size的一半,則從頭結點開始查找,否則從尾部開始查找(右移2位相當除以2)
if (index < (size >> 1)) {
Node<T> x = first.next;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<T> x = last.prev;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
在情況數據時,我們採用更利於GC回收的方式來處理數據:
/**
* 清空數據,GC更容易回收
* Blog : http://blog.csdn.net/javazejian
*/
@Override
public void clear() {
for (Node<T> x = first.next; x != null; ) {
Node<T> next = x.next;
x.data = null;
x.next = null;
x.prev = null;
x = next;
}
//初始化鏈表
first=new Node<>(null,null,null);
last=new Node<>(first,null,null);
first.next=last;
size = 0;
modCount++;
}
同時我們抽取了頭部插入,尾部插入,以及在某個結點前插入的代碼,由於存在頭結點和尾結點,無需判斷各種基於操作位置插入的情況,使代碼更簡潔化:
/**
* 在succ結點前插入
* Blog : http://blog.csdn.net/javazejian
*/
void linkBefore(T T, Node<T> succ) {
// assert succ != null;
final Node<T> newNode = new Node<>(succ.prev, T, succ);
succ.prev.next=newNode;
succ.prev = newNode;
size++;
modCount++;
}
/**
* 鏈表頭部添加,由於擁有頭結點和尾結點,無需判斷插入情況
* Blog : http://blog.csdn.net/javazejian
* @param data
*/
private void linkFirst(T data) {
//頭結點的下一個結點
final Node<T> f = first.next;
final Node<T> newNode = new Node<>(first, data, f);
f.prev=newNode;
first.next = newNode;
size++;
modCount++;
}
/**
* 鏈表尾部添加,由於擁有頭結點和尾結點,無需判斷插入情況
* Blog : http://blog.csdn.net/javazejian
* @param data
*/
void linkLast(T data) {
//尾部結點的前一個結點
final Node<T> l = last.prev;
final Node<T> newNode = new Node<>(l, data, last);
l.next = newNode;
last.prev=newNode;
size++;
//記錄修改
modCount++;
}
最後我們添加迭代器Iterator和fast-fail機制,還有點需要注意的是MyArrayList和MyLinkedList都實現了Iterable接口:
public class MyArrayList<T> implements Serializable,IList<T>,Iterable<T>
public class MylinkeList<T> implements Serializable,IList<T>, Iterable<T>
Iterable接口
/**
* Implementing this interface allows an object to be the target of
* the "for-each loop" statement. See
* <strong>
* <a href="{@docRoot}/../technotes/guides/language/foreach.html">For-each Loop</a>
* </strong>
*
* @param <T> the type of elements returned by the iterator
*
* @since 1.5
* @jls 14.14.2 The enhanced for statement
*/
public interface Iterable<T> {
實現該接口的同時我們必須實現iterator()並返回一個迭代器Itr
/**
* 返回迭代器
* @return
*/
@Override
public Iterator<T> iterator() {
return new Itr();
}
實現Iterable接口的另外一個好處是,我們的集合可以使用foreach增強for循環的方式來迭代元素,如果沒有實現Iterable接口是無法使用foreach增強for循環的方式迭代元素的,代碼示例如下:
System.out.println("-------------foreach--------------");
for(Integer data : mylinkeList){
System.out.println("data-->"+data);
}
好~,關於改良的鏈表我們就分析到這,因爲大部分實現的過程在前兩篇已分析得非常明白了,這裏也就沒必要重複了,最後貼一下實現源碼(需要注意的是Java JDK中的LinkedList內部結構和我們實現的並不一樣,LinkedList內部採用的雖然也有頭結點和尾結點,可頭結點和尾結點都分別指向第一個元素和最後一個元素,但它們的實現原理都是一樣的,大家不妨自己查閱一下源碼就明白了):
package com.zejian.structures.LinkedList.MyCollection;
import java.io.Serializable;
import java.util.*;
/**
* Created by zejian on 2016/11/10.
* Blog : http://blog.csdn.net/javazejian [請尊重原創,轉載註明出處]
* 改良的雙鏈表表(帶頭結點和尾結點)類似java集合類LinkedList
*/
public class MylinkeList<T> implements Serializable,IList<T>, Iterable<T>{
private static final long serialVersionUID = 8683452581122892300L;
/**
* 鏈表size,優化計算過程,無需遍歷鏈表
*/
private int size = 0;
/**
* 修改的記錄符
*/
private int modCount=0;
/**
* 頭部指向結點,不帶數據,排除特殊情況,優化代碼量
*/
private Node<T> first;
/**
* 尾部指向結點,不帶數據,排除特殊情況,優化代碼量
*/
private Node<T> last;
/**
* 初始化鏈表
*/
public MylinkeList() {
first=new Node<>(null,null,null);
last=new Node<>(first,null,null);
first.next=last;
size=0;
modCount++;
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size==0;
}
@Override
public boolean contains(T data) {
return indexOf(data)!=-1;
}
/**
* 清空數據,GC更容易回收
* Blog : http://blog.csdn.net/javazejian
*/
@Override
public void clear() {
for (Node<T> x = first.next; x != null; ) {
Node<T> next = x.next;
x.data = null;
x.next = null;
x.prev = null;
x = next;
}
//初始化鏈表
first=new Node<>(null,null,null);
last=new Node<>(first,null,null);
first.next=last;
size = 0;
modCount++;
}
/**
* 根據index查詢數據
* @param index
* @return
*/
@Override
public T get(int index) {
checkElementIndex(index);
return getNode(index).data;
}
@Override
public T set(int index, T data) {
//檢測下標是否越界
checkElementIndex(index);
Node<T> x = getNode(index);
T oldVal = x.data;
x.data = data;
return oldVal;
}
/**
* 尾部添加
* @param data
* @return
*/
@Override
public boolean add(T data) {
linkLast(data);
return true;
}
@Override
public void add(int index, T data) {
checkElementIndex(index);
if (index == size)//直接尾部添加
linkLast(data);
else
linkBefore(data, getNode(index));//查找到插入結點並在其前插入
}
@Override
public boolean remove(T data) {
if (data == null) {
for (Node<T> x = first.next; x != null; x = x.next) {
if (x.data == null) {
unlink(x);
return true;
}
}
} else {
for (Node<T> x = first; x != null; x = x.next) {
if (data.equals(x.data)) {
unlink(x);
return true;
}
}
}
return false;
}
@Override
public T remove(int index) {
checkElementIndex(index);
//移除
return unlink(getNode(index));
}
/**
* 根據值查下標
* Blog : http://blog.csdn.net/javazejian
* @param data
* @return
*/
@Override
public int indexOf(T data) {
int index = 0;
if (data == null) {
//注意起始結點
for (Node<T> x = first.next; x != null; x = x.next) {
if (x.data == null)
return index;
index++;
}
} else {
for (Node<T> x = first.next; x != null; x = x.next) {
if (data.equals(x.data))
return index;
index++;
}
}
return -1;
}
/**
* 根據data查詢最後一個下標
* Blog : http://blog.csdn.net/javazejian
* @param data
* @return
*/
@Override
public int lastIndexOf(T data) {
int index = size;
if (data == null) {
for (Node<T> x = last.prev; x != null; x = x.prev) {
index--;
if (x.data == null)
return index;
}
} else {
for (Node<T> x = last.prev; x != null; x = x.prev) {
index--;
if (data.equals(x.data))
return index;
}
}
return -1;
}
/**
* 刪除x結點
* Blog : http://blog.csdn.net/javazejian
* @param x
* @return
*/
T unlink(Node<T> x) {
// assert x != null;
x.next.prev=x.prev;
x.prev.next=x.next;
size--;
modCount++;
return x.data;
}
/**
* 在succ結點前插入
* Blog : http://blog.csdn.net/javazejian
*/
void linkBefore(T T, Node<T> succ) {
// assert succ != null;
final Node<T> newNode = new Node<>(succ.prev, T, succ);
succ.prev.next=newNode;
succ.prev = newNode;
size++;
modCount++;
}
/**
* 鏈表頭部添加,由於擁有頭結點和尾結點,無需判斷插入情況
* Blog : http://blog.csdn.net/javazejian
* @param data
*/
private void linkFirst(T data) {
//頭結點的下一個結點
final Node<T> f = first.next;
final Node<T> newNode = new Node<>(first, data, f);
f.prev=newNode;
first.next = newNode;
size++;
modCount++;
}
/**
* 鏈表尾部添加,由於擁有頭結點和尾結點,無需判斷插入情況
* Blog : http://blog.csdn.net/javazejian
* @param data
*/
void linkLast(T data) {
//尾部結點的前一個結點
final Node<T> l = last.prev;
final Node<T> newNode = new Node<>(l, data, last);
l.next = newNode;
last.prev=newNode;
size++;
//記錄修改
modCount++;
}
/**
* 優化結點查詢,根據情況而定查詢起點
* Blog : http://blog.csdn.net/javazejian
* @param index
* @return
*/
Node<T> getNode(int index) {
//如果index小於size的一半,則從頭結點開始查找,否則從尾部開始查找(右移2位相當除以2)
if (index < (size >> 1)) {
Node<T> x = first.next;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<T> x = last.prev;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
/**
* 判斷index是否越界
* Blog : http://blog.csdn.net/javazejian
* @param index
*/
private void checkElementIndex(int index) {
if (!(index >= 0 && index < size))
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
}
@Override
public Iterator<T> iterator() {
return new Itr();
}
/**
* 迭代器,支持變量過程刪除結點
* Blog : http://blog.csdn.net/javazejian
*/
private class Itr implements Iterator<T> {
/**
* 指向下一個結點的下標
*/
int cursor = 0;
/**
* 當前需要返回結點的下標
*/
int lastRet = -1;
/**
*用於判斷是否集合被修改
*/
int expectedModCount = modCount;
/**
* 是否還有下一個結點
* @return
*/
public boolean hasNext() {
return cursor != size();
}
/**
* 獲取當前結點的值
* @return
*/
public T next() {
checkForComodification();
try {
int i = cursor;
T next = get(i);
lastRet = i;//指向當前結點
cursor = i + 1;//更新,指向下一個還未訪問的結點
return next;
} catch (IndexOutOfBoundsException T) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
MylinkeList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;//回撤一位
lastRet = -1;//復原
expectedModCount = modCount;
} catch (IndexOutOfBoundsException T) {
throw new ConcurrentModificationException();
}
}
/**
* 檢測是否集合已變更
* 快速失敗機制
*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
public ListIterator<T> listIterator(int index) {
checkElementIndex(index);
return new ListItr(index);
}
/**
* 含前後指向的迭代器,支持變量過程添加元素,刪除元素
* Blog : http://blog.csdn.net/javazejian
*/
private class ListItr implements ListIterator<T> {
private Node<T> lastReturned;//指向當前正在被訪問的結點
private Node<T> next;//還未被訪問的結點
private int nextIndex;//還未被訪問的結點下標
private int expectedModCount = modCount;//用於判斷集合是否被修改
/**
* 結點指向傳入值index的結點
* @param index
*/
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : getNode(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
/**
* 獲取結點數據
* @return
*/
public T next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;//當前正在被訪問的結點
next = next.next;//更新至還未被訪問的結點
nextIndex++;//更新至還未被訪問結點的下標
return lastReturned.data;
}
/**
* 是否有前驅結點
* @return
*/
public boolean hasPrevious() {
return nextIndex > 0;
}
/**
* 功能與next()一樣,但previous()是往前遍歷
* @return
*/
public T previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last.prev : next.prev;
nextIndex--;
return lastReturned.data;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
/**
* 移除操作
*/
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<T> lastNext = lastReturned.next;
unlink(lastReturned);
//如果next還未更新,則直接執行lastNext
if (next == lastReturned)
next = lastNext;
else
//如果next已更新,那麼nextIndex必定已執行了nextIndex++操作,此時由於刪除結點
//所以必須執行nextIndex--,才能使nextIndex與next相對應
nextIndex--;
//復原
lastReturned = null;
expectedModCount++;
}
public void set(T T) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.data = T;
}
public void add(T T) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(T);
else
linkBefore(T, next);
nextIndex++;
expectedModCount++;
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
/**
* 雙向結點類
* Blog : http://blog.csdn.net/javazejian
* @param <T>
*/
private static class Node<T> {
T data;
Node<T> next;
Node<T> prev;
Node(Node<T> prev, T data, Node<T> next) {
this.data = data;
this.next = next;
this.prev = prev;
}
}
//測試
public static void main(String[] args){
System.out.println("------init-------");
MylinkeList<Integer> mylinkeList=new MylinkeList<>();
mylinkeList.add(2);
mylinkeList.add(10);
mylinkeList.add(1);
mylinkeList.add(9);
mylinkeList.add(20);
mylinkeList.add(555);
print(mylinkeList);
System.out.println("------remove(2)-------");
mylinkeList.remove(2);
print(mylinkeList);
System.out.println("------indexOf(10)&set(0,0)-------");
System.out.println("index-->"+mylinkeList.indexOf(10));
mylinkeList.set(0,0);
print(mylinkeList);
System.out.println("-------------iterator--------------");
Iterator<Integer> iterator=mylinkeList.iterator();
while (iterator.hasNext()){
System.out.println("iterator.next-->"+iterator.next());
}
System.out.println("-------------iteratorList--------------");
ListIterator<Integer> iteratorList=mylinkeList.listIterator(0);
iteratorList.add(88);
while (iteratorList.hasNext()){
System.out.println("iteratorList.next-->"+iteratorList.next());
}
iteratorList.add(100);
System.out.println("-------------iteratorList1.add--------------");
//使用完後必須重新new
ListIterator<Integer> iteratorList1=mylinkeList.listIterator(0);
while (iteratorList1.hasNext()){
int i=iteratorList1.next();
if(i==555){
System.out.println("i==555");
iteratorList1.remove();
}else {
System.out.println("iteratorList.next-->" +i);
}
}
System.out.println("-------------foreach--------------");
for(Integer data : mylinkeList){
System.out.println("data-->"+data);
}
System.out.println("-------------iterator--------------");
//拋異常:java.util.ConcurrentModificationException
//在迭代時刪除元素必須使用iterator自身的刪除方法,使用mylinkeList的
//刪除方法將會觸發快速失敗機制
Iterator<Integer> it = mylinkeList.iterator();
while (it.hasNext()) {
mylinkeList.remove(new Integer(100));
Integer value = it.next();
if (value==100) {
System.out.println("該集合含100!");
}else {
System.out.println("該集合不含100!");
}
}
}
public static void print(MylinkeList mylinkeList){
for (int i=0;i<mylinkeList.size();i++) {
System.out.println("i->"+mylinkeList.get(i));
}
}
}
源碼地址如下:
github源碼下載,歡迎star(含文章列表,持續更新)
關聯文章:
java數據結構與算法之順序表與鏈表設計與實現分析
java數據結構與算法之雙鏈表設計與實現
java數據結構與算法之改良順序表與雙鏈表類似ArrayList和LinkedList(帶Iterator迭代器與fast-fail機制)