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。一個集合說一兩個應該就夠了,畢竟是面試入場題,感覺深度比廣度重要。