分析完了List
與Queue
以後,終於能夠看看LinkedList
的實現了。LinkedList
彌補了ArrayList
增刪較慢的問題,但在查找方面又遜色於ArrayList
,因此在使用時須要根據場景靈活選擇。對於這兩個頻繁使用的集合類,掌握它們的源碼並正確使用,可讓咱們的代碼更高效。java
LinkedList
既實現了List
,又實現了Deque
,前者使它可以像使用ArrayList
同樣使用,後者又使它可以承擔隊列的職責。LinkedList
內部結構是一個雙向鏈表,咱們在分析ArrayDeque
時提到過這個概念,就是擴充單鏈表的指針域,增長一個指向前一個元素的指針previous。node
AbstractSequentialList
是LinkedList
的父級,它繼承自AbstractList
,而且是一個抽象類,它主要爲順序表的鏈式實現提供一個骨架:dom
This class provides a skeletal implementation of the List interface to minimize the effort required to implement this interface backed by a "sequential access" data store (such as a linked list). For random access data (such as an array), AbstractList should be used in preference to this class.ide
意思是它的主要做用是提供一個實現List
接口的骨架,來減小咱們實現基於鏈式存儲的實現類時所需的工做量。AbstractSequentialList
並無作不少特殊的事情,其中最主要的是提供一個方法的默認實現,並將如下方法抽象,以期有更符合場景的實現:函數
public abstract ListIterator<E> listIterator(int index);
其餘一些方法的實現都利用了這個listIterator
方法,咱們再也不一一查看了。下面咱們分析LinkedList
的實現性能
LinkedList
的繼承結構以下所示:學習
能夠看到,LinkedList
也實現了Cloneable
、java.io.Serializable
等方法,借鑑於ArrayList
的經驗,咱們能夠想到它的Clone
也是淺克隆,在序列化方法也採用了一樣的方式,咱們就再也不贅述了。ui
在介紹鏈表結構時提到過,其數據單元分爲數據域和指針域,分別存儲數據和指向下一個元素的位置,在java中只要定義一個實體類就能夠解決了。this
private static class Node<E> { E item; //數據 Node<E> next; //下一個元素 Node<E> prev; //上一個元素 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
LinkedList
成員變量主要有三個,並且其意義清晰可見。指針
// 記錄當前鏈表的長度 transient int size = 0; // 第一個節點 transient Node<E> first; // 最後一個節點 transient Node<E> last;
由於鏈表沒有長度方面的問題,因此也不會涉及到擴容等問題,其構造函數也十分簡潔了。
public LinkedList() { } public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
一個默認的構造函數,什麼都沒有作,一個是用其餘集合初始化,調用了一下addAll
方法。addAll
方法咱們就再也不分析了,它應該是和添加一個元素的方法是一致的。
LinkedList
既繼承了List
,又繼承了Deque
,那它必然有一堆add
、remove
、addFirst
、addLast
等方法。這些方法的含義也相差不大,實現也是相似的,所以LinkedList
又提取了新的方法,來簡化這些問題。咱們看看這些不對外的方法,以及它們是如何與上述函數對應的。
//將一個元素連接到首位 private void linkFirst(E e) { //先將原鏈表存起來 final Node<E> f = first; //定義一個新節點,其next指向原來的first final Node<E> newNode = new Node<>(null, e, f); //將first指向新建的節點 first = newNode; //原鏈表爲空表 if (f == null) //把last也指向新建的節點,如今first與last都指向了它 last = newNode; else //把原鏈表掛載在新建節點,也就是如今的first以後 f.prev = newNode; size++; modCount++; } //與linkFirst相似 void linkLast(E e) { //... } //在某個非空節點以前添加元素 void linkBefore(E e, Node<E> succ) { // assert succ != null; //先把succ節點的前置節點存起來 final Node<E> pred = succ.prev; //新節點插在pred與succ之間 final Node<E> newNode = new Node<>(pred, e, succ); //succ的prev指針移到新節點 succ.prev = newNode; //前置節點爲空 if (pred == null) //說明插入到了首位 first = newNode; else //把前置節點的next指針也指向新建的節點 pred.next = newNode; size++; modCount++; } //刪除首位的元素,元素必須非空 private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC first = next; if (next == null) last = null; else next.prev = null; size--; modCount++; return element; } private E unlinkLast(Node<E> l) { //... } //刪除一個指定的節點 E unlink(Node<E> x) { //... }
能夠看到,LinkedList
提供了一系列方法用來插入和刪除,可是卻沒有再實現一個方法來進行查詢,由於對鏈表的查詢是比較慢的,因此它是經過另外的方法來實現的,咱們看一下:
public E get(int index) { checkElementIndex(index); return node(index).item; } //能夠說盡力了 Node<E> node(int index) { // assert isElementIndex(index); //size>>1就是取一半的意思 //折半,將遍歷次數減小一半 if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
最後,咱們看下它如何對應那些繼承來的方法:
//引用了node方法,須要遍歷 public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; } //也可能須要遍歷 public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); } //也要遍歷 public E remove(int index) { checkElementIndex(index); return unlink(node(index)); } public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; } public E element() { return getFirst(); } public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } public E remove() { return removeFirst(); } public boolean offer(E e) { return add(e); } public boolean offerFirst(E e) { addFirst(e); return true; } //...
LinkedList
很是適合大量數據的插入與刪除,但其對處於中間位置的元素,不管是增刪仍是改查都須要折半遍歷,這在數據量大時會十分影響性能。在使用時,儘可能不要涉及查詢與在中間插入數據,另外若是要遍歷,也最好使用foreach
,也就是Iterator
提供的方式。
【感謝您能看完,若是可以幫到您,麻煩點個贊~】
更多經驗技術歡迎前來共同窗習交流: 一點課堂-爲夢想而奮鬥的在線學習平臺 http://www.yidiankt.com/
![關注公衆號,回覆「1」免費領取-【java核心知識點】]
QQ討論羣:616683098
QQ:3184402434
想要深刻學習的同窗們能夠加我QQ一塊兒學習討論~還有全套資源分享,經驗探討,等你哦!