LinkedList 底層數據結構是一個雙向鏈表,總體結構以下圖所示:
node
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; } }
新增節點時,咱們能夠選擇追加到鏈表頭部,仍是追加到鏈表尾部,add 方法默認是從尾部開始追加,addFirst 方面試
法是從頭部開始追加:算法
/** * 將元素添加到鏈表尾部,等於addFirst */ public boolean add(E e) { linkLast(e); return true; }
/** * 從尾部開始追加節點 */ void linkLast(E e) { // 把尾節點數據暫存 final Node<E> l = last; // 新建新的節點,初始化入參含義: // l 是新節點的前一個節點,當前值是尾節點值 // e 表示當前新增節點,當前新增節點後一個節點是 null final Node<E> newNode = new Node<>(l, e, null); // 將新建節點追加到尾部 last = newNode; //若是鏈表爲空 l 是尾節點,尾節點爲空,鏈表即空,頭部和尾部是同一個節點,都是新建的節點 if (l == null) first = newNode; else //不然把前尾節點的下一個節點,指向當前尾節點。 l.next = newNode; //大小和版本更改 size++; modCount++; }
/** *從頭部開始追加節點 */ private void linkFirst(E e) { // 把頭節點數據暫存 final Node<E> f = first; // 新建新的節點,初始化入參含義: // l 是新節點的前一個節點,當前值是尾節點值 // f 表示當前新增節點,當前新增節點後一個節點是 null final Node<E> newNode = new Node<>(null, e, f); // 將新建節點追加到頭部 first = newNode; //若是鏈表爲空 f 是頭節點,頭節點爲空,鏈表即空,頭部和尾部是同一個節點,都是新建的節點 if (f == null) last = newNode; else //上一個頭節點的前一個節點指向當前節點 f.prev = newNode; size++; modCount++; }
LinkedList節點刪除的方式和追加相似,咱們能夠選擇從頭部刪除,也能夠選擇從尾部刪除,刪除操做會把節點的值,先後指spring
向節點都置爲 null。數據庫
/** * 從頭部開始刪除節點 */ private E unlinkFirst(Node<E> f) { //拿出頭節點的值,做爲方法的返回值 final E element = f.item; // 拿出頭節點的下一個節點 final Node<E> next = f.next; //幫助 GC 回收頭節點 f.item = null; f.next = null; //頭節點的下一個節點成爲頭節點 first = next; //若是 next 爲空,代表鏈表爲空 if (next == null) last = null; else //鏈表不爲空,頭節點的前一個節點指向 null next.prev = null; //修改鏈表大小和版本 size--; modCount++; return element; }
/** * 和從頭部刪除基本一致 */ private E unlinkLast(Node<E> l) { // assert l == last && l != null; final E element = l.item; final Node<E> prev = l.prev; l.item = null; l.prev = null; // help GC last = prev; if (prev == null) first = null; else prev.next = null; size--; modCount++; return element; }
/** * 根據鏈表索引位置查詢節點 */ Node<E> node(int index) { // 若是 index 處於隊列的前半部分,從頭開始找,size >> 1 是 size 除以 2 的意思。 if (index < (size >> 1)) { Node<E> x = first; // 直到 for 循環到 index 的前一個 node 中止 for (int i = 0; i < index; i++) x = x.next; return x; } else { // 若是 index 處於隊列的後半部分,從尾開始找 Node<E> x = last; // 直到 for 循環到 index 的後一個 node 中止 for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
LinkedList 並無採用從頭循環到尾的作法,而是採起了二分法,首先看 index 是在鏈表的前半部分,仍是後半部分。若是是前半部分,就從頭開始尋找,反之亦然。經過這種方式,使循環的次數 至少下降了一半,提升了查找的性能。設計模式
get() 獲取第幾個元素,依次遍歷,複雜度O(n)
add(E) 添加到末尾,複雜度O(1)
add(index, E) 添加第幾個元素後,須要先查找到第幾個元素,直接指針指向操做,複雜度O(n) (這個比較容易想錯)
remove()刪除元素,直接指針指向操做,複雜度O(1)數組
只有當 LinkedList做爲共享變量時,纔會有線程安全問題,當 LinkedList是方法內的局部變量時,是沒有線程安全的問題的。緩存
LinkedList有線程安全問題的緣由,是由於 LinkedList自身的 size、modConut 在進行各類操做時,都沒有加鎖,並且這些變量的類型並不是是可見(volatile)的,因此若是多個線程對這些變量進行操做時,可能會有值被覆蓋的狀況。安全
類註釋中推薦使用 Collections#synchronizedList 來保證線程安全,SynchronizedList 是經過在每一個方法上面加上鎖來實現,雖然實現了線程安全,可是性能大大下降,具體實現源碼:數據結構
public boolean add(E e) { synchronized (mutex) {return c.add(e);} }
咱們也可使用ConcurrentLinkedQueue來保證線程安全,
LinkedList的底層是鏈表結構 ,適用於適合於常常新增和刪除的場景, 歡迎關注公衆號:前程有光,領取一線大廠Java面試題總結+各知識點學習思惟導+一份300頁pdf文檔的Java核心知識點總結! 這些資料的內容都是面試時面試官必問的知識點,篇章包括了不少知識點,其中包括了有基礎知識、Java集合、JVM、多線程併發、spring原理、微服務、Netty 與RPC 、Kafka、日記、設計模式、Java算法、數據庫、Zookeeper、分佈式緩存、數據結構等等。