LinkedList源碼分析,linkedlist源碼

來源:互聯網
上載者:User

LinkedList源碼分析,linkedlist源碼

1.類說明

  實現了List和Deque介面的雙向鏈表。實現了List的所有操作,允許所有元素(包含null)。

  所有的操作基本都是可以雙向進行的,對列表的索引操作會從列表的開頭或者結尾開始,取決於那邊比較接近該索引的位置。

  LinkedList也不是安全執行緒的,如果需要有多線程對此集合進行操作的情境,並且其中一個會修改該集合的結構(只包含刪除和新增操作,不包括設值操作)時,都必須要保證該集合的同步性。我們可以使用Collections.synchronizedList(new LinkedList(...))這個方法來擷取一個同步集合。

  快速失敗機制和ArrayList一樣,不再贅述。

2.繼承和實現

  該類繼承了AbstractSequentialList,實現了List、Deque、Cloneable和Serializable。

  AbstractSequentialList:大致意思就是說如果一個list是以"順序訪問"作為其資料存放區(比如Linkedlist),可以繼承該類,它會為你提供一個骨架實現,從而最大限度地減少實現此介面所需的工作。對於隨機訪問資料(如ArrayList),應該優先使用AbstractList,而不是先使用此類。如果我們自己要實現一個列表,只需要擴充此類,並提供listIterator和size方法的實現即可。對於不可修改的列表,只需要實現列表迭代器的hasNext、next、hasPrevious、previous和index方法即可。對於可修改的列表,我們需要再另外實現列表迭代器的set方法。對於可變大小的列表,需要再另外實現列表迭代器的remove和add方法。從某種意義上說,此類與在列表的列表迭代器上實現“隨機訪問”方法(get(int index)、set(int index, Object element)、set(int index, Object element)、add(int index, Object element) 和 remove(int index))的AbstractList類相對立,而不是其他關係(雖然是繼承AbstractList)。當前我們應該不會自己去建立list。

  Deque:double-ended queue,就是雙端隊列,支援雙端插入和刪除,實現了Queue(隊列-FIFO,棧-LIFO),可以插入null值但是非常不建議,因為很多方法都會把null值當做是空隊列的特定傳回值。

  List、Cloneable和Serializable不再贅述。

3.成員變數

  int size:該集合對應的元素數量

  Node<E> first/last:分別指向該集合的第一個元素和最後一個元素

  modCount:結構性修改的次數

4.構造方法

 1 public LinkedList() { 2 // 產生一個空List集合 3 } 4  5 // 將c集合中的資料初始化到List集合中 6 // 如果c為null,會拋出null 指標異常 7 public LinkedList(Collection<? extends E> c) { 8     this(); 9     // 下面介紹成員方法時介紹10     addAll(c);11 }

5.成員方法

  首先要瞭解LinkedList是基於什麼實現的,再來看這些方法會容易很多。LinkedList主要是基於結點(Node)實現的,1所示:

  

圖1  Node類

  通過這個Node我們就能很好的瞭解以下的一些方法了。

List操作:

  void linkFirst(E e):將該元素設定為集合第一個元素,首元素prev為null,linkedList.addFirst(E e)調用該方法

  void linkLast(E e):將該元素設定為集合最後一個元素,尾元素next為null,LinkedList的addLast(E e)、add(E e)和add(E e,int index)調用該方法

  void linkBefore(E e,Node<E> succ):將e元素插入succ結點之前,主要是LinkedList的add(E e)調用此方法,操作如下

 1     void linkBefore(E e, Node<E> succ) { 2         // assert succ != null;判斷succ不可為空 3         // 取出succ的前一個node 4         final Node<E> pred = succ.prev; 5         // 建立一個新結點,將e的前結點設定為succ的前結點,將e的後結點設定為succ 6         final Node<E> newNode = new Node<>(pred, e, succ); 7         // 將succ的前結點設定為建立的結點 8         succ.prev = newNode; 9         // 如果succ的前結點為空白,則succ為集合頭結點,將新結點設定為頭結點10         if (pred == null)11             first = newNode;12         else13             pred.next = newNode;// 不然的話將pred的後結點設定為新結點,這樣就算插入完成了14         // 增加集合大小15         size++;16         modCount++;17     }
linkBefore

  E unlinkFirst(Node<E> f):刪除並返回該集合的首元素,需要確保f元素是首元素並且不為null,linkedList的removeFirst()、poll()方法和pollFirst()調用該方法

  E unlinkLast(Node<E> l):刪除並返回該集合的尾元素,需要確保l元素是尾元素並且不為null,linkedList的removeLast()、pollLast()調用該方法

  E unlink(Node<E> x):刪除x結點並返回x的屬性值,確保x不為空白,linkedList的remove(Object o)、remove(int index)、removeLastOccurrence(Object o)調用此方法,操作如下

 1     E unlink(Node<E> x) { 2         final E element = x.item; 3         final Node<E> next = x.next; 4         final Node<E> prev = x.prev; 5         // 首結點的判斷 6         if (prev == null) { 7             first = next; 8         } else { 9             prev.next = next;10             x.prev = null;11         }12         // 尾結點的判斷13         if (next == null) {14             last = prev;15         } else {16             next.prev = prev;17             x.next = null;18         }19 20         x.item = null;21         size--;22         modCount++;23         return element;24     }
unlink

  E getFirs()、E getLast()、E removeFirst()、E removeLast()、void addFirst(E e)、void addLast(E e)、int size()、boolean add(E e)方法相對簡單,不再贅述

  boolean contains(Object o):判斷o是否存在該集合中,其中的indexOf(Object o)方法是返回o在集合中的第一次出現的位置,沒有則返回-1

  boolean remove(Object o):刪除集合中第一次出現的o元素,o可以是null

  boolean addAll(Collection<? extends E> c):將集合c全部添加到該list尾部,如果c為空白,拋null 指標異常

  boolean addAll(int index, Collection<? extends E> c):在指定的index位插入c集合,步驟如下:

 1     public boolean addAll(int index, Collection<? extends E> c) { 2         // 檢查index的合理性,必須屬於[0,size],否則拋出IndexOutOfBoundsException 3         checkPositionIndex(index); 4         Object[] a = c.toArray(); 5         int numNew = a.length; 6         if (numNew == 0) { 7             return false; 8         } 9         // succ-要被插入的結點,也就是從這個節點之後開始新增,pred-上一個結點10         Node<E> pred, succ;11         // 如果index==size,上一個結點就是尾元素12         if (index == size) {13             succ = null;14             pred = last;15         } else {16             // 如果index < (size >> 2),從頭元素開始遍曆集合,不然從尾元素開始遍曆17             succ = node(index);18             pred = succ.prev;19         }20         for (Object o : a) {21             @SuppressWarnings("unchecked")22             E e = (E) o;23             // 每個的結點next為空白,預設為尾結點24             Node<E> newNode = new Node<>(pred, e, null);25             // pred為空白表示是頭結點,將第一個產生的結點作為第一個結點26             if (pred == null)27                 first = newNode;28             else29                 pred.next = newNode;30             // 為下一次迴圈做準備31             pred = newNode;32         }33         // 尾結點後續處理34         if (succ == null) {35             last = pred;36         } else {37             pred.next = succ;38             succ.prev = pred;39         }40         size += numNew;41         modCount++;42         return true;43     }
addAll

  void clear()清空集合、E get(int index)擷取index位置的結點的值(item)、E set(int index, E element)將e元素設定到index對應的結點、add(int index, E element)在index之後新增e元素結點、E remove(int index)刪除index位對應的結點,相對簡單,不再贅述。

  Node<E> node(int index):屬於工具方法,擷取index結點對應的值,要確保index值得合理性。源碼的if判斷是一個精髓,如果index < (size >> 1),從頭開始遍曆,否則從尾部開始遍曆,效率最大化

  int indexOf(Object o)和int lastIndexOf(Object o):二者都是查詢元素所在位置,不同的是一個從頭開始,一個從尾開始

Queue(FIFO)操作:

  E peek():擷取集合的第一個元素值,不會將該資料從集合中刪除,不存在返回null

  E element():也是擷取集合第一個元素,與上述方法基本相同,只是如果不存在會拋出NoSuchElementException

  E poll():取出第一個元素,會將該資料從集合中刪除,不存在返回null

  E remove():移除第一個元素,於poll基本相同,如果不存在該元素會拋出NoSuchElementException

  boolean offer(E e):向集合末尾追加元素,成功返回true

Deque(雙端隊列,既可以FIFO,也可以LIFO,看實現)操作:

  boolean offerFirst(E e)、boolean offerLast(E e):向集合頭部/尾部追加元素,成功返回true

  E peekFirst()、E peekLast():擷取但是不取出頭/尾元素

  E pollFirst()、E pollLast():擷取並移除頭/尾元素

  void push(E e):向集合頭部添加元素

  E pop():擷取並移除頭元素,不存在拋出NoSuchElementException

  boolean removeFirstOccurrence(Object o)、boolean removeLastOccurrence(Object o):刪除第一個/最後一個元素為o的結點

  Object clone():對該集合的淺複製,只是複製了引用地址,並不會對元素複製(會有指向該元素的引用)

    LinkedList<E> superClone():調用父類clone方法,一般會成功,如果JVM出異常,會拋出InternalError

  Object[] toArray():將集合變成數組並返回,返回的數組是安全的,並且與原來的集合沒有任何關聯,也就是給這個數組重新分配了記憶體

  T[] toArray(T[] a):將集合變成指定類型的數組,跟ArrayList的基本一致,只是多了個從結點取值的操作

  void writeObject(java.io.ObjectOutputStream s)、void readObject(java.io.ObjectInputStream s):集合序列化/還原序列化的過程

  Iterator<E> descendingIterator():返回一個從尾部到頭部的迭代器,和listIterator是反向的

6.jdk1.8新方法

  Spliterator<E> spliterator():擷取一個並行遍曆集合的迭代器(splitable iterator可分割迭代器),詳見:https://segmentfault.com/q/1010000007087438

7.ArrayList和LinkedList的區別

  由於最近有跳槽的打算,所以經曆了一些面試。面試的開場問題一般都是談談你瞭解的集合,一般我們用的集合有三種,List、Set和Queue,均是繼承於Collection,具體可以展開很多。我在這裡就大致歸納一下這二者的區別,如下:

  ArrayList是數組大小可變的實現,是基於數組實現的。這也使得它在擷取、修改元素的時候很快(O(1))。新增的情況分為兩種吧,如果是在集合末尾新增,ArrayList比LinkedList要快,如果是在集合中間插入元素,則LinkedList比ArrayList快,因為ArrayList多了一步將元素移位的操作,相對耗時,刪除同理。插入的測試可以看以下代碼

 1 public class AddTest { 2     public static void main(String[] args) { 3         List<String> arrList = new ArrayList<String>(); 4         List<String> linkList = new LinkedList<String>(); 5         long startTime = System.currentTimeMillis(); 6         // ArrayList執行1000000次插入,這種插入屬於在集合末尾插入 7         for (int i = 0; i < 1000000; i++) { 8             arrList.add("123"); 9         }10         long endTime = System.currentTimeMillis();11         System.out.println("arr add time:" + (endTime - startTime));//arr add time:1412         // LinkedList執行1000000插入,這種插入屬於在集合末尾插入13         startTime = System.currentTimeMillis();14         for (int i = 0; i < 1000000; i++) {15             linkList.add("123");16         }17         endTime = System.currentTimeMillis();18         System.out.println("link add time:" + (endTime - startTime));//link add time:6119         // 不停的在index為100的位置新增20         startTime = System.currentTimeMillis();21         for (int i = 0; i < 100; i++) {22             arrList.add(100, "234");23         }24         endTime = System.currentTimeMillis();25         System.out.println("arr index add time:" + (endTime - startTime));//arr index add time:4926         // 不停的在index為100的位置新增27         startTime = System.currentTimeMillis();28         for (int i = 0; i < 100; i++) {29             linkList.add(100, "234");30         }31         endTime = System.currentTimeMillis();32         System.out.println("link index add time:" + (endTime - startTime));//link index add time:033     }34 }
View Code

  LinkedList是鏈表的方式,基於結點實現的。使得它在新增、刪除時候優於ArrayList,因為只需要更改一下結點上、下結點的指向就可以了,不用移動元素。但是修改、擷取的時候比較慢,因為需要遍曆集合,執行時間是O(n)。

  這就是二者的基本總結,如果你可以添加對CopyOnWriteArraySet的介紹應該會加分。當然這些都屬於基礎,答出來是應該的。

後記:最近有跳槽的打算,所以CopyOnWriteArrayList等這些同步集合之後才會去看源碼,接下來應該是set和map的分析,set主要是hashSet,有可能有TreeSet,Map主要就是我用到的兩個,HashMap、ConcurrentHashMap,可能會有LinkedHashMap和TreeSet。一個集合說一兩個應該就夠了,畢竟是面試入場題,感覺深度比廣度重要。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.