ArrayList,LinkedList原始碼解析
在java中,集合這一資料結構應用廣泛,應用最多的莫過於List介面下面的ArrayList和LinkedList;
我們先說List,
1 public interface List<E> extends Collection<E> { 2 //返回list集合中元素的數量,若數量大於Integer.MAX_VALUE,則返回Integer.MAX_VALUE 3 int size(); 4 5 //判讀集合內是否沒有元素,若沒有元素返回true 6 boolean isEmpty(); 7 8 //判斷集合內是否包含指定的元素o9 boolean contains(Object o); 10 11 //以適當的序列,返回該集合元素中的一個迭代器 12 Iterator<E> iterator(); 13 14 //返回一個數組,該陣列包含該集合中所有的元素(以從first到last的順序) 15 Object[] toArray(); 16 17 //把集合中的元素放到陣列a中,並返回 18 <T> T[] toArray(T[] a); 19 20 21 //向集合末尾中新增一個元素 22 boolean add(E e); 23 24 //從集合中刪除第一個出現的元素o25 boolean remove(Object o); 26 27 28 //判斷集合中是否包含 指定集合c中的所有元素 29 boolean containsAll(Collection<?> c); 30 31 //將指定集合c中的所有元素都追加到 集合的末尾 32 boolean addAll(Collection<? extends E> c); 33 34 //將指定集合c中的所有元素都插入到 集合中,插入的開始位置為index 35 boolean addAll(int index, Collection<? extends E> c); 36 37 //將指定集合c中的所有元素從本集合中刪除38 boolean removeAll(Collection<?> c); 39 40 //本集合和 集合c的交集 41 boolean retainAll(Collection<?> c); 42 43 //清除集合中的元素 44 void clear(); 45 46 //比較指定物件o和本集合是否相等,只有指定物件為list,size大小和本集合size一樣,且每個元素equal一樣的情況下,才返回true 47 boolean equals(Object o); 48 49 50 int hashCode(); 51 52 //返回指定位置index的元素 53 E get(int index); 54 55 //將元素element設定到集合的index位置(替換) 56 E set(int index, E element); 57 58 //將元素element插入到集合的index位置 59 void add(int index, E element); 60 61 //移除指定位置index的元素 62 E remove(int index); 63 64 //返回指定物件o在本集合中的第一個索引位置 65 int indexOf(Object o); 66 67 //返回指定物件o在本集合中的最後一個索引位置 68 int lastIndexOf(Object o); 69 70 //返回一個ListIterator的迭代器 71 ListIterator<E> listIterator(); 72 73 //從指定位置index開始返回一個ListInterator迭代器 74 ListIterator<E> listIterator(int index); 75 76 //返回從位置fromIndex開始到位置toIndex結束的子集合 77 List<E> subList(int fromIndex, int toIndex); 78 }
下面我們看一看ArrayList,ArrayList是基於陣列的方式來實現資料的增加、刪除、修改、搜尋的。
ArrayList內部維護者兩個變數:
//該陣列快取者集合中的元素,集合的容量就是該陣列的長度,elementData用transient修飾,說明在序列化時,陣列elementData不在序列化範圍內。 private transient Object[] elementData; //集合的大小 (集合中元素的數量) private int size;
我們再看一看ArrayList的構造器:
/** *構造一個指定容量initialCapacity的空的集合, *super() 是呼叫AbstractList的預設構造器方法,該方法是一個空的方法, *然後判斷傳入的引數initialCapacity不能小於0,否則就直接丟擲非法引數異常; *最後直接建立了一個長度為initialCapacity的陣列物件,並將該物件賦值給當前例項的elementData屬性,用以存放集合中的元素。 */ public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } /** *構造一個預設的容量為10的空的集合,我們平時最經常使用的List<T> list = new ArrayList<T>();是預設建立了容量為10的集合。 */ public ArrayList() { this(10); }
/**
*構造一個包含了指定集合c中的所有元素的集合,該集合中元素的順序是以集合c的迭代器返回元素的順序。
*/ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }
從上面的原始碼中我們看到,先將c.toArray()方法的返回值賦值給elementData,將elementData.length賦值給size, 然後進行了一個判斷 if(elementData.getClass() != Object[].class),若為真,則呼叫Arrays.copyOf()方法建立一個新Object[]陣列,將原來elementData中的元素copy到新建的Object[]陣列中,最後將新建的陣列賦值給elementData。
我們看一下Arrays.copyOf()方法的原始碼:
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)); return copy; }
如果newType型別為Object[].class的話,則直接建立一個長度為newLength的Object陣列,否則使用Array.newInstance(Class<?> componentType, int length)方法建立一個元素型別為newType.getComponentType() (該方法返回陣列中元素的型別)型別的,長度為newLength的陣列,這是一個native方法,然後使用System.arraycopy() 這個native方法將original陣列中的元素copy到新建立的陣列中,並返回該陣列。
我們注意 c.toArray might (incorrectly) not return Object[],按理說一個c.toArray()返回的是一個Object[]型別,其getClass()返回的也一定是Object[].class,那為什麼還要進行逐個判斷呢? 可真實情況真的是這樣嗎?我們看下面的示例:
//定義一個父類Animal public class Aniaml { } //定義Animal的子類Person public class Person extends Aniaml{ private int id; private String name; private Date birthday; public Person(int id, String name, Date birthday) { this.id = id; this.name = name; this.birthday = birthday; } public static void main(String[] args) { test1(); test2(); test3(); } public static void test1(){ Person[] persons = {new Person(100,"lewis",new Date()),new Person(100,"lewis",new Date())}; //class [Lcom.lewis.guava.Person; Person的陣列型別 System.out.println(persons.getClass()); Aniaml[] aniamls = persons; //class [Lcom.lewis.guava.Person; Person的陣列型別 System.out.println(aniamls.getClass()); //class com.lewis.guava.Person Person型別 System.out.println(aniamls[0].getClass()); //java.lang.ArrayStoreException animals[]陣列中實際儲存的是Peron型別,當執行時放入非Person型別時會報錯ArrayStoreException aniamls[0] = new Aniaml(); } public static void test2(){ List<String> list = Arrays.asList("abc"); //class java.util.Arrays$ArrayList 由此可見該型別不是ArrayList型別 System.out.println(list.getClass()); Object[] objects = list.toArray(); //class [Ljava.lang.String; 返回的是String陣列型別 System.out.println(objects.getClass()); //java.lang.ArrayStoreException: java.lang.Object 當我們將一個Object放入String陣列時報錯ArrayStoreException objects[0] = new Object(); } public static void test3(){ List<String> dataList = new ArrayList<String>(); dataList.add(""); dataList.add("hehe"); //class java.util.ArrayList System.out.println(dataList.getClass()); Object[] objects1 = dataList.toArray(); //class [Ljava.lang.Object; System.out.println(objects1.getClass()); objects1[0]="adf"; objects1[0]=123; objects1[0]=new Object(); } }
我們由上面test2()方法可知,一個List,呼叫list.toArray()返回的Object陣列的真實型別不一定是Object陣列([Ljava.lang.Object;),當我們呼叫 Object[] objArray = collection.toArray(), objArray並不一定能夠存放Object物件,所以上面的原始碼中對這種情況進行了判斷。
我們接著看ArrayList中的方法:
add(E),當我們新增資料的時候,會遇到的一個問題就是:當裡面的陣列滿了,沒有可用的容量的怎麼辦?
/** *插入物件,首先將size+1,產生一個minCapacity的變數,呼叫ensureCapacity(minCapacity)方法保證陣列在插入一個元素時有可用的容量,然後將元素e放到陣列elementData的size位置,最後將size+1 */ public boolean add(E e) { ensureCapacity(size + 1); // Increments modCount!! elementData[size++] = e; return true; } /** *必要時陣列的擴容 (如果想調整ArrayList的容量增長策略,可以繼承ArrayList,override ensureCapacity()方法即可),
首先將modCount+1,modeCount表示修改陣列結構的次數(維護在父類AbstractList中),如果入參minCapacity大於目前陣列elementData的容量,則將容量擴充套件到 (oldCapacity * 3)/2 + 1,
若此時容量仍然小於 minCapacity,則直接將minCapacity設定為最新的容量,
最後使用Arrays.copyof()方法將原來elementData中元素copy到新的陣列中,並將新陣列賦值給elementData. */ public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } }
/** *在指定下標Index處插入元素element,首先判斷下標index的引數是否有效(不能小於0,不能大於size),否則丟擲IndexOutOfBoundsException異常,然後呼叫ensureCapacity(size+1)要確保陣列中有足夠的容量來插入資料,
*然後呼叫System.arraycopy()方法, 從index下標開始,將elementData陣列元素copy到 從index+1開始的地方,copy的長度為size-index,這樣index下標處的位置就空了出來,然後將元素element放到下標為index處的位置,最後將size++.
*我們看到使用這個方法相比add(E),多了一步陣列元素的複製的代價。 */ public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); ensureCapacity(size+1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
/** *將元素element設值到下標為index處,返回原來index處的舊值。 */ public E set(int index, E element) { RangeCheck(index);
//獲取index下標的舊值 E oldValue = (E) elementData[index];
//設值新的元素element到index處 elementData[index] = element; return oldValue; } //邊界檢查,index越界的話,丟擲異常IndexOutOfBoundsException private void RangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); }
/** *將指定集合c中的元素按照其迭代器返回的順序追加到本集合中,只要將任何一個元素插入到集合中都會返回true */ public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; //確保(size+a.length)有足夠的容量去插入新元素 ensureCapacity(size + numNew); // Increments modCount //將陣列a的內容追加到elementData陣列的最後 System.arraycopy(a, 0, elementData, size, numNew); size += numNew;
//只要插入的元素個數>0就返回true return numNew != 0; }
我們再看刪除的方法
/** *刪除指定元素o,分為兩種情況,若指定元素o為null,則遍歷當前的elementData陣列,如果某一個下標index上面的值為null(==),則呼叫方法fastRemove(int)快速刪除該元素,然後返回true
*若指定元素o不為0,則遍歷elementData陣列,若某一個下標index處的元素和指定元素o 進行equals 為true的話,則呼叫fastRemove(int)快速刪除該元素,然後返回true
*由下面的原始碼可知,只能刪除第一個匹配的元素。 */ public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } /** *快速刪除指定下標index的元素,不做邊界檢查(該方法時private的) */ private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0)
//裡面進行了陣列元素的移動,將index後面的元素往前複製一位 System.arraycopy(elementData, index+1, elementData, index, numMoved);
//將陣列elementData中最後一個位置置為null,以便釋放已用,讓gc 回收 elementData[--size] = null; // Let gc do its work }
/** *刪除指定下標index處的元素,該方法相比remove(Object o)方法,多了一個邊界檢查,但是少了元素的查詢過程,因此效能更好一些。 */ public E remove(int index) {
//對入參index做邊界檢查 RangeCheck(index); modCount++;
//取出index位置的元素 E oldValue = (E) elementData[index]; int numMoved = size - index - 1; if (numMoved > 0)
//進行陣列元素的移動 System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work //返回原來index位置的舊元素 return oldValue; }
元素的搜尋:
/** *獲取指定下標index處的元素,先進行邊界檢查,然後直接返回elementData陣列中對應位置index處的元素。 */ public E get(int index) { RangeCheck(index); return (E) elementData[index]; }
/** *判斷集合中是否包含指定元素o,呼叫indexOf(Object o)方法實現 */ public boolean contains(Object o) { return indexOf(o) >= 0; } /** *返回指定元素o在陣列elementData首次出現的下標,這裡也是分為兩種情況:
*1.指定元素o為null 2.指定元素o不為null,在查詢元素的過程中,o為null,使用 == 比較,o不為null,使用equals比較,若找到了該元素,則返回其在陣列elementData中的下標index,若找不到這返回-1. */ 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; }
與indexOf(Object o)方法類似的是 lastIndexOf(Object o)方法,不同的是 後者返回的是最後一次出現指定元素o的位置下標。
public int lastIndexOf(Object o) { if (o == 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 (o.equals(elementData[i])) return i; } return -1; }
我們再看一下ArrayList的迭代器方法如何實現的:
/** *該方法是有ArrayList的父類AbstractList持有的,返回的是一個Itr物件 */ public Iterator<E> iterator() { return new Itr(); }
我們看看Itr是個什麼鬼:
/** *Itr 實現了Iterator介面,是AbstractList中的一個內部類。 */ private class Itr implements Iterator<E> { //當前迭代器指向的陣列中元素的下一個元素的位置 int cursor = 0; int lastRet = -1; int expectedModCount = modCount; //每次呼叫hasNext方法時,判斷當前指向的陣列中的位置和陣列大小是否相等,若不等則返回true(說明還有值),若相等則返回false(說明已經迭代到了陣列的末尾了,沒有元素了) public boolean hasNext() { return cursor != size(); } //呼叫next方法是,先呼叫checkForComodification()方法,判斷是否有其他執行緒對集合大小做出了有影響的動作;
//然後呼叫get方法獲取相應位置的元素,若獲取不到,則丟擲IndexOutOfBoundsException異常,在捕獲到該異常後,呼叫checkForComodification()方法,
//檢測modcount若== expectedModCount(沒有其他執行緒對集合大小做出了有影響的操作),則丟擲NoSuchElementException,若modcount != expectedModCount,則丟擲ConcurrentModificationException
public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } //若modCount和期望的expectedModCount不相等,說明在迭代過程中,有其他的執行緒對集合大小產生了影響的動作(新增、刪除),此時丟擲異常ConcurrentModificationException final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
我們在看一個方法trimToSize
/** *將elementData陣列的容量 縮小為該集合實際包含的元素數量 */ public void trimToSize() { modCount++; int oldCapacity = elementData.length; if (size < oldCapacity) { elementData = Arrays.copyOf(elementData, size); } }
使用ArrayList的注意事項:
1.ArrayList是基於陣列的方式實現的,可以通過下標索引直接查詢到指定位置的元素,因此查詢效率高,但每次插入或刪除元素,就要大量地移動元素,插入刪除元素的效率低。
2.ArrayList在插入元素時,可能會進行陣列的擴容,但是在刪除元素時卻不會減小陣列的容量,如果希望減小陣列的容量,可使用trimToSize方法,在查詢元素要遍歷陣列時,對非 null元素使用equals方法,對null元素使用==。
3.擴充容量的方法ensureCapacity。ArrayList在每次增加元素(可能是1個,也可能是一組)時,都要呼叫該方法來確保足夠的容量。當 容量不足以容納當前的元素個數時,就設定 新的容量為舊的容量的1.5倍加1,如果設定後的新容量還不夠,則直接新容量設定為傳入的引數(也就是所需的容 量),而後用Arrays.copyof()方法將元素拷貝到新的陣列。從中 可以看出,當容量不夠時,每次增加元素,都要將原來的元 素拷貝到一個新的陣列中,非常之耗時,也因此建議在事先能確定元素數量的情況下,才使用ArrayList,否則建議使用 LinkedList
4.ArrayList不是執行緒安全的。
接著我們看下LinkedList,LinkedList是基於雙向連結串列的方式來實現的,雙向連結串列就是集合中的每個元素都知道其前面的一個元素的位置和它後的一個元素位置。
在LinkedList中,使用一個內部類Entry來表示集合中的節點,元素的值賦值給element屬性,節點的next屬性指向下一個節點,節點的previous屬性指向前一個節點。
private static class Entry<E> {
//element存放資料 E element;
//next屬性指向當前節點的下一個節點 Entry<E> next;
//previous屬性指向當前節點的上一個節點 Entry<E> previous; Entry(E element, Entry<E> next, Entry<E> previous) { this.element = element; this.next = next; this.previous = previous; } }
/** *維護了一個header節點,header節點的element屬性為null,previouse屬性為null,next屬性為null,並且header節點是不儲存資料的 */ private transient Entry<E> header = new Entry<E>(null, null, null); //size表示集合包含的元素個數 private transient int size = 0; /** * 構造一個空的集合,將header節點的next屬性、previous屬性都指向header節點,這樣形成了一個雙向連結串列的閉環 */ public LinkedList() { header.next = header.previous = header; }
新增操作
/** *追加指定的元素e到集合尾部,其效果和方法 addLast(E e)是一致的,都是呼叫addBefore(e,header)方法。 */ public boolean add(E e) { addBefore(e, header); return true; } /** *將資料e 插入到元素entry前面(private級別,LinkedList內部呼叫,意味著不能直接在自己的應用程式中呼叫此方法,但是可以利用反射API來間接的呼叫(好想沒必要這樣做)) */ private Entry<E> addBefore(E e, Entry<E> entry) { //建立一個新節點newEntry,newEntry.element屬性設定為e,newEntry.next屬性設定為entry,newEntry.previous屬性設定為entry.previous Entry<E> newEntry = new Entry<E>(e, entry, entry.previous); ///將newEntry.previous節點的next屬性指向newEntry本身 newEntry.previous.next = newEntry; //將newEntry.next節點的previous屬性指向newEntry本身 newEntry.next.previous = newEntry; //將集合大小size + 1 size++; //集合大小影響次數modCount + 1 modCount++; //返回新建立的節點 return newEntry; }
下面我們看一下新增一個節點,在集合中的直接檢視情況:
假設我們一開始建立一個LinkedList時,只有一個header節點(不儲存資料的),如下圖所示:
有一個元素A1插入到了header的前面。
現在要插入一個元素A2,在執行完下面程式碼後,
Entry<E> newEntry = new Entry<E>(A2, header, header.previous); //將newEntry的next屬性指向header節點,newEntry.previous屬性指向header.previous指向的節點(A1);
newEntry.previous.next = newEntry; //將newEntry.previous節點(A1)的next屬性指向newEntry,即將A1.previous屬性指向A2。
newEntry.next.previous = newEntry;//將newEntry.next節點(header)的previous屬性指向newEntry,即將header.previous屬性指向A2.
圖形變成了下面的樣子:
我們看一下LinkedList的一個帶參的建構函式:
/** *以指定集合c的迭代器返回的節點順序,構造一個包含指定集合c中所有元素的集合 */ public LinkedList(Collection<? extends E> c) { //這裡this()呼叫了LinkedList的無參構造器,使header.next=header.previous=header,即此時僅有一個header節點 this(); //呼叫addAll(c)方法 addAll(c); } /** *將指定集合c中所有的元素,按照其迭代器返回的順序全部追加到集合的結尾。 */ public boolean addAll(Collection<? extends E> c) { return addAll(size, c); } /** *將指定集合c中所有的元素,按照其迭代器返回的順序,從指定的位置index開始全部插入到集合中。 */ public boolean addAll(int index, Collection<? extends E> c) { //對引數index做邊界檢查,無效丟擲IndexOutOfBoundsException if (index < 0 || index > size) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size); //將集合c 轉換為陣列 Object[] a = c.toArray(); //獲取陣列的長度 int numNew = a.length; //若陣列的長度為0,說明陣列是空的,沒有可以插入的元素,則直接返回false if (numNew==0) return false; //程式走到這,說明有可以插入的資料了,集合大小肯定會改變,因此modCount+1 modCount++; //如果入參index==size,則將header節點設定為後繼節點successor,否則呼叫entry(int)方法求出位置index的節點,並將該節點設定為後繼節點successor. Entry<E> successor = (index==size ? header : entry(index)); //獲取後繼節點successor的前驅節點predecessor Entry<E> predecessor = successor.previous; //迴圈陣列中的元素,進入資料的插入 for (int i=0; i<numNew; i++) { //建立一個新節點e,將陣列a的第i個元素a[i]作為新節點e的element屬性,successor作為e的next節點,predescessor作為e的previous節點 Entry<E> e = new Entry<E>((E)a[i], successor, predecessor); //將predecessor的next屬性指向新節點e predecessor.next = e; //由於還要進行後續的插入,因此將新節點e設定為前驅節點predecessor,以便繼續迴圈 predecessor = e; } //在迴圈陣列中的元素插入完成後,將sucessor.previous屬性指向predecessor節點,此時已經完成了雙向連結串列的閉環了。 successor.previous = predecessor; //將集合大小size 增加numNew個 size += numNew; return true; } /** *返回指定位置index的節點 */ private Entry<E> entry(int index) { //對引數index做邊界檢查,無效丟擲IndexOutOfBoundsException if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size); //將頭結點header設定為e Entry<E> e = header; //如果入參index小於陣列大小size的一半,即index< size/2的話,則從前往後查詢(從下標為0開始,到index),否則從後往前查詢(從下標為size開始,到index+1) if (index < (size >> 1)) { for (int i = 0; i <= index; i++) //從前往後遍歷 e = e.next; } else { for (int i = size; i > index; i--) //從後往前遍歷 e = e.previous; } //找到後返回 return e; }
其他的新增操作:
/** *追加指定元素e到集合的結尾,效果和呼叫add(E e)方法是一樣的(都是呼叫方法addBefore(e,header)),只是該方法沒返回值 */ public void addLast(E e) { addBefore(e, header); } /** *將指定元素e插入到集合的開始位置,呼叫方法addBefore(e,header.next)實現 */ public void addFirst(E e) { //這裡插入在header.next節點的前面,因此可以認為是集合的開始位置 addBefore(e, header.next); } /** *在指定位置index處插入指定的元素e */ public void add(int index, E element) { //若index== size,即要追加到集合的結尾處,即插入到header之前;否則呼叫entry(int)方法獲取index處的節點,插入到該節點之前 addBefore(element, (index==size ? header : entry(index))); } /** *將元素element設值到位置為index處(即將原index處的值替換為element),並返回原來index處的值 */ public E set(int index, E element) { Entry<E> e = entry(index); E oldVal = e.element; e.element = element; return oldVal; } /** *追加元素e到集合結尾 */ public boolean offer(E e) { return add(e); } /** *將元素e插入集合的開始位置,呼叫addFirst(E e)方法實現 */ public boolean offerFirst(E e) { addFirst(e); return true; } /** *將元素e插入集合的結束位置,呼叫addLast(E e)方法實現 */ public boolean offerLast(E e) { addLast(e); return true; } /** *將元素e推入(push)進入棧 (LinkedList也可以當做棧使用) */ public void push(E e) { addFirst(e); }
刪除操作:
/** *刪除指定元素在集合中的第一個出現的節點(下標index最小的) */ public boolean remove(Object o) { //分為兩種情況:1.入參o 為null,使用== 比較 2.入參o不為null,使用equals比較 //若o == null if (o==null) { //從前往後開始遍歷(從header節點的下一個節點開始) for (Entry<E> e = header.next; e != header; e = e.next) { //使用 == 比較 if (e.element==null) { //找到第一個為null的節點,呼叫方法remove(Entry e)刪除該節點 remove(e); //返回true,表示刪除成功 return true; } } } else { //從前往後開始遍歷(從header節點的下一個節點開始) for (Entry<E> e = header.next; e != header; e = e.next) { //使用 equals 比較 if (o.equals(e.element)) { //找到第一個equals為true的節點,呼叫方法remove(Entry e)刪除該節點 remove(e); //返回true,表示刪除成功 return true; } } } //返回false,表示沒有找到要刪除的元素o return false; } /** *刪除指定的節點e(該方法是私有的,供LinkedList內部呼叫,不對外提供),與ArrayList的刪除操作而言,LinkedList的刪除操作不需要進行陣列的移動,而是僅僅改變下被刪除元素的前後兩元素的指向而已,因此LinkedList刪除操作效率更高。 */ private E remove(Entry<E> e) { //判斷節點是否是頭結點header,我們知道header節點不儲存資料,若入參e被發現實header節點,則丟擲NoSuchElementException異常。 if (e == header) throw new NoSuchElementException(); //獲取節點e的儲存的資料result E result = e.element; //將節點e的前一個節點的next屬性直接指向e的下一個節點(斷開e.previous.next=e這個鏈條) e.previous.next = e.next; //將節點e的下一個節點的previous屬性直接指向e的前一個節點(斷開e.next.previous=e這個鏈條) e.next.previous = e.previous; //將e的next屬性和previous屬性都設定為null(斷開e對前一個節點和後一個節點的指向的鏈條) e.next = e.previous = null; //將e的本身儲存的資料元素element置為null e.element = null; //size 大小減一 size--; //由於remove操作影響了集合的結構(大小),因此modCount+1 modCount++; //返回被刪除節點的儲存資料result return result; }
清除集合中的所有節點clear()方法:
/** *清除集合中所有的節點 */ public void clear() { //獲取header節點的下一個節點e Entry<E> e = header.next; //只要遍歷一遍集合即可 while (e != header) { //e節點的下一個節點next Entry<E> next = e.next; //將節點e的next屬性和previous屬性都設定為null(不指向任何節點) e.next = e.previous = null; //將節點e的儲存資料元素置為null e.element = null; //將next節點設定為當前迴圈的節點e e = next; } //迴圈刪除了集合中的所有有資料的元素後,只保留header節點(不儲存資料),並組成一個閉環 header.next = header.previous = header; //由於清理了所有的元素,此時size 設定為0 size = 0; //此操作涉及到了集合結構大小的改變,因此modCount + 1 modCount++; }
查詢元素在集合中的下標indexOf()方法和lastIndexOf()
/** *返回元素o在集合中首次出現的位置下標 */ public int indexOf(Object o) { //維護一個位置索引index用了記錄變量了多少個元素,從0開始 int index = 0; //若o == null,則使用 == 比較,從前往後 if (o==null) { for (Entry e = header.next; e != header; e = e.next) { if (e.element==null) //找到了第一個為null的,則返回其下標index return index; //在當前迴圈中沒有找到,則將下標index+1 index++; } } else { //若o不為 null,則使用 equals 比較,從前往後 for (Entry e = header.next; e != header; e = e.next) { if (o.equals(e.element)) //找到了第一個equals為truel的,則返回其下標index return index; //在當前迴圈中沒有找到,則將下標index+1 index++; } } //遍歷完整個連結串列沒有找到的話,返回-1 return -1; } /** *返回元素o在集合中最後出現的位置下標 */ public int lastIndexOf(Object o) { //維護一個位置索引index用了記錄變量了多少個元素,從最大size開始 int index = size; //若o == null,則使用 == 比較,從後往前 if (o==null) { for (Entry e = header.previous; e != header; e = e.previous) { //先將index-1 index--; if (e.element==null) //找到了第一個遇見為null的,則返回其下標index return index; } } else { //若o != null,則使用 equals 比較,從後往前 for (Entry e = header.previous; e != header; e = e.previous) { //先將index-1 index--; if (o.equals(e.element)) //找到了第一個遇見equals為truel的,則返回其下標index return index; } } //在遍歷集合一遍之後,沒有找到元素的,返回-1 return -1; }
判斷集合是否包含某個元素:
/** *判斷集合是否包含指定元素o,呼叫indexOf(Object o)來實現的 */ public boolean contains(Object o) { return indexOf(o) != -1; }
/** *獲取指定位置index的元素,呼叫entry(int )來實現,entry(int)這個方法上面已講過 */ public E get(int index) { return entry(index).element; }
將集合轉換為陣列:
/** *按照集合中從前往後的順序,返回一個包含集合中所有元素的陣列 */ public Object[] toArray() { //建立一個同集合大小一樣的Object陣列result Object[] result = new Object[size]; //維護一個下標索引i,用以表示陣列 元素的下標 int i = 0; //從前往後遍歷連結串列 for (Entry<E> e = header.next; e != header; e = e.next) //將遍歷節點e的儲存資料放到陣列result中的第i個位置,然後i+1 result[i++] = e.element; //返回result陣列 return result; } /** *給一個數組a,返回一個數組(陣列元素按照集合從前往後的順序排列),該陣列包含了集合中的所有元素 */ public <T> T[] toArray(T[] a) { //若陣列a的長度不足以裝入所有的集合元素,則使用Array.newInstance()這一方法建立一個size大小,元素型別為陣列a的元素型別的陣列,並將該陣列賦值給a if (a.length < size) a = (T[])java.lang.reflect.Array.newInstance( a.getClass().getComponentType(), size); int i = 0; Object[] result = a; //從前往後遍歷連結串列,依次將集合中的元素放入陣列a中 for (Entry<E> e = header.next; e != header; e = e.next)
//將遍歷節點e的儲存資料放到陣列result中的第i個位置,然後i+1 result[i++] = e.element; //若陣列a的長度超過了size,則將陣列a中size下標置為null if (a.length > size) a[size] = null; //返回陣列a return a; }
LinkedList的迭代器:
/** *返回一個list的迭代器,直接new了一個內部類ListItr來實現的 */ public ListIterator<E> listIterator(int index) { return new ListItr(index); } /** *ListItr是LinkedList的私有內部類,實現了ListIterator介面 */ private class ListItr implements ListIterator<E> { //將header設定為最近一次返回的節點 private Entry<E> lastReturned = header; //下一次要返回的節點 private Entry<E> next; //下次要返回節點的下標 private int nextIndex; //將目前修改集合結構大小的記錄數賦值給expectedModCount ,用於比較在一個執行緒遍歷集合時,是否有其他的執行緒在同步修改該集合結構 private int expectedModCount = modCount; //入參為int的建構函式 ListItr(int index) { //對入參進行邊界檢查,如無效則丟擲IndexOutOfBoundsException if (index < 0 || index > size) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size); //若index < size/2,則從前往後遍歷nextIndex從0到index-1 if (index < (size >> 1)) { //將header節點的next屬性賦值給next,即下一次要返回的節點 next = header.next; //獲取指定位置的節點 for (nextIndex=0; nextIndex<index; nextIndex++) next = next.next; } else { //若index >= size/2,則從後往前遍歷nextIndex從size到index next = header; //獲取指定位置的節點 for (nextIndex=size; nextIndex>index; nextIndex--) next = next.previous; } } //根據nextIndex是否等於size來判斷是否還有沒有遍歷的節點 public boolean hasNext() { return nextIndex != size; } //獲取下一個元素 public E next() { //檢查本集合在遍歷的過程中,是否有其他執行緒對本集合的結構大小做了修改 checkForComodification(); //nextIndex == size,說明連結串列已經被遍歷了一遍,不存在沒有被遍歷的節點了 if (nextIndex == size) throw new NoSuchElementException(); //將next節點設定為最近一次返回的節點 lastReturned = next; //將next節點往後移動一位 next = next.next; //將nextIndex+1 nextIndex++; //返回最近一次返回節點的資料元素 return lastReturned.element; } //從後往前遍歷的過程中,判斷是否還有未被遍歷的元素 public boolean hasPrevious() { return nextIndex != 0; } //獲取上一個元素 public E previous() { //若nextIndex==0,說明在從後往前遍歷(下邊從size 到 0)過程中,已經遍歷到頭了,不存在沒有被遍歷的節點了,則丟擲NoSuchElementException if (nextIndex == 0) throw new NoSuchElementException(); //將next往前移動一位,並將next節點賦值給最近返回的節點lastReturned lastReturned = next = next.previous; //將nextIndex-1 nextIndex--; //檢查本集合在遍歷的過程中,是否有其他執行緒對本集合的結構大小做了修改 checkForComodification(); //返回最近一次返回節點的資料元素 return lastReturned.element; } public int nextIndex() { return nextIndex; } public int previousIndex() { return nextIndex-1; } //移除當前迭代器持有的節點 public void remove() { checkForComodification(); Entry<E> lastNext = lastReturned.next; try { LinkedList.this.remove(lastReturned); } catch (NoSuchElementException e) { throw new IllegalStateException(); } if (next==lastReturned) next = lastNext; else nextIndex--; lastReturned = header; expectedModCount++; } //將當前迭代器持有的元素 替換為元素e public void set(E e) { if (lastReturned == header) throw new IllegalStateException(); checkForComodification(); lastReturned.element = e; } //在當前節點後面插入元素e public void add(E e) { checkForComodification(); lastReturned = header; addBefore(e, next); nextIndex++; expectedModCount++; } //檢查本集合在遍歷的過程中,是否有其他執行緒對本集合的結構大小做了修改,如果別的執行緒對集合做出了修改,則丟擲ConcurrentModificationException final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } /** *介面ListIterator,繼承了迭代器介面Iterator */ public interface ListIterator<E> extends Iterator<E> { //在遍歷集合時(按照從前往後的順序),是否還存在沒有遍歷的元素 boolean hasNext(); //返回下一個元素 E next(); //在遍歷集合時(按照從後往前的順序),是否還存在沒有遍歷的元素 boolean hasPrevious(); //返回上一個元素 E previous(); //返回下一個元素的位置下標 int nextIndex(); //返回上一個元素的位置下標 int previousIndex(); //刪除正在遍歷的元素 void remove(); //將正在遍歷的元素替換為 元素e void set(E e); //插入元素e void add(E e); }
使用LinkedList的注意事項:
1.LinkedList是基於雙向連結串列實現的。
2.LinkedList在插入元素時,必須建立Entry物件,並修改相應元素的前後元素的引用;在查詢元素時,必須遍歷連結串列;在刪除元素時,遍歷連結串列找到要刪除的元素,修改被刪除元素的前後元素的引用;
3.LinkedList不是執行緒安全的。
我們再看一看ArrayList的構造器: