1. 程式人生 > >java8 LinkedList原始碼閱讀【2】- 總結

java8 LinkedList原始碼閱讀【2】- 總結

上一篇文章 java8 LinkedList原始碼閱讀已經分析了LinkedList原始碼,現對LinkedList做個小結。

  • LinkedList特點
    • 雙向連結串列實現,因此沒有固定容量,不需要擴容
    • 元素時有序的,輸出順序與輸入順序一致
    • 允許元素為 null
    • 所有指定位置的操作都是從頭開始遍歷進行的
    • 和 ArrayList 一樣,不是同步容器
    • 需要更多的記憶體,LinkedList 每個節點中需要多儲存前後節點的資訊,佔用空間更多些。
    • 查詢效率低,插入刪除效率高。
  • LinkedList結構
    繼承AbstractSequentialList並實現了List介面,Deque介面,Cloneable介面,Serializable介面,因此它支援佇列操作,可複製,可序列化。
public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  • LinkedList本質
    LinkedList是個雙向連結串列,它有三個成員變數。
//連結串列節點個數
transient int size = 0;

//頭節點指標
transient Node<E> first;

//尾節點指標
transient Node<E> last;

其中節點是一個雙向節點

//節點實現
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;
    }
}
  • modCount變數[fail-fast機制]

    類似ArrayList,LinkedList也維護了modCount變數,其記錄了陣列的修改次數,在LinkedList的所有涉及結構變化的方法中都增加modCount的值。
    該變數迭代器等方面體現。
//檢查連結串列是否修改,根據expectedModCount和modCount判斷
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

因此在使用迭代器迭代過程中,不允許對連結串列結構做修改(如插入新節點),否則會丟擲異常 java.util.ConcurrentModificationException。

  • indexOf(Object o)
    該方法會根據是否為null使用不同方式判斷。如果是元素為null,則直接比較地址,否則使用equals的方法比較,加快比較效率。lastIndexOf(Object o) 同理。
// 獲得指定元素在連結串列第一次出現的下標,不存在返回-1
public int indexOf(Object o) {
    int index = 0;
    //根據指定元素是否為null採取不同比較方式,加快比較
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}
  • clone() 淺拷貝
//返回副本,淺拷貝,與ArrayList.clone()相似
public Object clone() {
    LinkedList<E> clone = superClone(); //將clone構造成一個空的雙向迴圈連結串列

    // Put clone into "virgin" state
    clone.first = clone.last = null;
    clone.size = 0;
    clone.modCount = 0;

    // Initialize clone with our elements
    for (Node<E> x = first; x != null; x = x.next)
        clone.add(x.item);  //淺拷貝,節點還是同一份引用

    return clone;
}

舉個例子如下:

public class Main {
    static class A {
        int a;

        A(int a) {
            this.a = a;
        }

        @Override
        public String toString() {
            return super.toString() + " : " + a;
        }
    }

    public static void main(String[] args) {
        LinkedList<A> a1=new LinkedList<>();
        a1.add(new A(1));
        a1.add(new A(2));
        LinkedList<A> a2= (LinkedList<A>) a1.clone();
        System.out.println(a1);
        System.out.println("----");
        System.out.println(a2);
        System.out.println("----");

        a2.get(1).a=100;
        System.out.println(a1);
        System.out.println("----");
        System.out.println(a2);
        System.out.println("----");
    }
}
/*
 * 輸出結果:
 [[email protected] : 1, [email protected] : 2]
 ----
 [[email protected] : 1, [email protected] : 2]
 ----
 [[email protected] : 1, [email protected] : 100]
 ----
 [[email protected] : 1, [email protected] : 100]
 ----
 */

從上述輸出結果可以看出,儘管克隆的連結串列不是跟原連結串列同一塊記憶體,但內容引用是同樣的,指向同一個地址,可以從輸出結果看出,因此改變克隆後連結串列的元素內容,相應的原連結串列內容發生相應變化,因此clone()方法是淺拷貝。
(注:對於基本型別,如int,則不會發生這種情況。)

  • toArray() 和 toArray(T[] a)
    跟上述clone()方法類似,同樣也是淺拷貝。
//返回一個包含此列表中所有元素的陣列
public Object[] toArray() {
    Object[] result = new Object[size];
    int i = 0;
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;
    return result;
}

//返回一個數組,使用執行時確定型別,該陣列包含在這個列表中的所有元素(從第一到最後一個元素)
//如果引數陣列容量比連結串列節點數少,則返回連結串列陣列;否則覆蓋引數陣列前size位,且第size位賦null,剩餘不變。
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
    //如果引數陣列容量不夠,則重新申請容量足夠的陣列
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.newInstance(
                a.getClass().getComponentType(), size);
    int i = 0;
    Object[] result = a;
    //遍歷依次覆蓋
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;

    if (a.length > size)
        a[size] = null;

    return a;
}
  • clear()
    因為底層實現不是陣列,LinkedList中的 clear方法稍微複雜一些,需要對每個節點的所有屬性置null。
//清空連結串列
public void clear() {
    // Clearing all of the links between nodes is "unnecessary", but:
    // - helps a generational GC if the discarded nodes inhabit
    //   more than one generation
    // - is sure to free memory even if there is a reachable Iterator
    for (Node<E> x = first; x != null; ) {
        Node<E> next = x.next;
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    first = last = null;
    size = 0;
    modCount++;
}
  • node(int index)
    該方法獲得指定位置index的節點,其實現雖然也是遍歷連結串列,但由於該連結串列是雙向連結串列,因此支援雙向查詢。查詢前會根據指定位置index判斷是在連結串列的前半段還是後半段,從而決定是從前往後找或是從後往前找,提升查詢效率。
// 獲得指定位置的節點
Node<E> node(int index) {
    // 這裡判斷指定位置是在前半段還是後半段,從而決定從後遍歷或從前遍歷,加快效率
    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;
    }
}
  • Queue 佇列操作
    由於LinkedList實現了Deque介面,而Deque繼承了Queue,因此LinkedList也可以進行佇列操作,包括:
// 佇列操作,獲取表頭節點的值,表頭為空返回null
public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

// 佇列操作,獲取表頭節點的值,表頭為空丟擲異常
public E element() {
    return getFirst();
}

// 佇列操作,獲取表頭節點的值,並刪除表頭節點,表頭為空返回null
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);
}
  • Deque 雙向佇列操作
    由於LinkedList實現了Deque介面,因此可用於雙向佇列。
// 雙向佇列操作,連結串列首部插入新節點
public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}

// 雙向佇列操作,連結串列尾部插入新節點
public boolean offerLast(E e) {
    addLast(e);
    return true;
}

// 雙向佇列操作,獲取連結串列頭節點值
public E peekFirst() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

// 雙向佇列操作,獲取尾節點值
public E peekLast() {
    final Node<E> l = last;
    return (l == null) ? null : l.item;
}

// 雙向佇列操作,獲取表頭節點的值,並刪除表頭節點,表頭為空返回null
public E pollFirst() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

// 雙向佇列操作,獲取表尾節點的值,並刪除表尾節點,表尾為空返回null
public E pollLast() {
    final Node<E> l = last;
    return (l == null) ? null : unlinkLast(l);
}
  • listIterator(int index)
// 返回從指定位置開始的ListIterator迭代器
public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);  //檢查位置合法性
    return new ListItr(index);
}

其中ListItr是LinkedList的一個內部類,實現了ListIterator介面,是個支援雙向的迭代器。其實現細節較長,就不貼了,見上一節原始碼解析即可。

  • descendingIterator()
// 返回一個迭代器在此雙端佇列以逆向順序的元素
public Iterator<E> descendingIterator() {
    return new DescendingIterator();
}

// DescendingIterator的實現,從後往前的迭代
private class DescendingIterator implements Iterator<E> {
    private final ListItr itr = new ListItr(size());    //獲得連結串列尾部的ListItr
    public boolean hasNext() {
        return itr.hasPrevious();
    }
    public E next() {
        return itr.previous();
    }
    public void remove() {
        itr.remove();
    }
}

利用上面實現的雙向迭代器類ListItr,可輕易得實現了逆向的迭代器。

  • spliterator()
    java8新增方法,類似Iiterator, 可以理解為 Iterator 的 Split 版本,可用於多執行緒,仍需學習。

【引用網上的說明】
使用 Iterator 的時候,我們可以順序地遍歷容器中的元素,使用 Spliterator 的時候,我們可以將元素分割成多份,分別交於不於的執行緒去遍歷,以提高效率。

使用 Spliterator 每次可以處理某個元素集合中的一個元素 — 不是從 Spliterator 中獲取元素,而是使用 tryAdvance() 或 forEachRemaining() 方法對元素應用操作。

但 Spliterator 還可以用於估計其中儲存的元素數量,而且還可以像細胞分裂一樣變為一分為二。這些新增加的能力讓流並行處理程式碼可以很方便地將工作分佈到多個可用執行緒上完成。

  • 同步問題
    LinkedList 和 ArrayList 一樣,不是同步容器。所以需要外部做同步操作,或者直接用 Collections.synchronizedList 方法包一下:
List list = Collections.synchronizedList(new LinkedList(...));

此時list是執行緒安全類,自身提供的方法也是執行緒安全的。當然list進行其他非原子操作仍需自己同步。

相關推薦

java8 LinkedList原始碼閱讀2- 總結

上一篇文章 java8 LinkedList原始碼閱讀已經分析了LinkedList原始碼,現對LinkedList做個小結。 LinkedList特點 雙向連結串列實現,因此沒有固定容量,不需要擴容 元素時有序的,輸出順序與輸入順序一致 允許元素為 nu

Spring原始碼分析2-Tomcat和Sping的連線點

Tomcat是怎麼呼叫上Spring的呢?需要找到這個連線點。 答案就在org.apache.catalina.startup.ContextConfig的processServletContainerInitializers方法 new WebappServiceLo

Elasticsearch 5.6.12 原始碼——2啟動過程分析(上)

版權宣告:本文為博主原創,轉載請註明出處! 簡介 本文主要解決以下問題: 1、啟動ES的入口類及入口方法是哪個?2、分析梳理ES服務啟動的主要流程? 入口類 ES的入口類為org.elasticsearch.bootstrap.Elasticsearch,啟動方法為: public

遊戲開發閱讀列表2動畫(Anima2D、粒子、物理等)

遊戲中動畫的實現有很多不同方法,幀動畫、骨骼動畫、基於物理的動畫、基於Shader的動畫、粒子等。 在這篇文章中,列出了我最近讀到過的不同種類動畫入門級的文章、視訊。關於Unity動畫狀態機這一類太常見的,這裡不再贅述。 所分享的內容都親自品嚐,保證無毒無害,營養價值極高!

2019-1-17水晶報表技巧總結2

ron har int函數 false ide 改進 資料 公式 資源管理器 第一條:水晶報表分組分頁且每頁最多顯示N條記錄 要求:1、詳細節最多5條記錄(不能超過5條); 2、無論前一組是否滿5條記錄,每個新組都要另起一頁 3

NOIP2017提高A組衝刺11.2總結

不用說了,我連大眾分都沒拿到。不過做得好的一點是,該拿的分我都拿了。 第一題看了5秒就切了,這不就是拓補排序加個優先佇列嗎? 第二題:期望。我提醒自己不要慌,記得期望的線性性,即和的期望等於期望的和

JAVA面試題總結2

一、基礎知識: 1、JVM、JRE和JDK的區別:    JVM(Java Virtual Machine):java虛擬機器,用於保證java的跨平臺的特性。              ja

Spring-Security2DelegatingFilterProxy

pat security clas 添加 chain let XML org mapping Spring Security 對我們應用的影響是通過一系列的 ServletRequest 過濾器實現的。 Spring Security 使用了 o.s.web.filter

2JVM-JAVA對象的訪問

lin oar XML nts java棧 article value new string Java中對象的訪問 JAVA是面向對象的語言,那麽在JAVA虛擬機中,存在非常多的對象,對象訪問是無處不在的。即時是最簡單的訪問,也會涉及到JAVA棧、JAVA堆、方法區

Android組件系列----ContentProvider內容提供者2

resolv blank lan int 復制 pad otto rtp wrap 二、代碼舉例: 終於全部project文件的文件夾結構例如以下: PersonDao是增刪改查數據庫的工具類,並在PersonContentProvider中得到調用。DBHe

quick-cocos2d-x遊戲開發2——項目結構分析、創建新場景

fileutil 遊戲 log world plain ack 設計 avi sca 創建完一個新項目之後,我們能夠簡單的看一看這個項目的文件組成,有這麽一個文件層次結構 幾個proj.*目錄就不用說了,是相應的平臺的解決方式,res專門存放我們的遊戲資源

Cocos2d-x v3.0正式版嘗鮮體驗2 Android平臺移植

生成 ble ack nts 做的 導入 eclipse so文件 腳本 今天沒事又嘗試了下3.0正式版關於Android平臺的移植,把新建的項目移植了下。過程僅用了十分鐘左右,什麽概念?!好吧,事實上我想說,這個版本號真的移植非常輕松啊,只是還沒加上其它東西,只是就眼

java持有對象2ArrayList容器續解

對象 符號 向上 ont 轉換 選擇 同時 是什麽 object 此為JDK API1.6.0對ArrayList的解釋。 ArrayList 使用java泛型創建類很復雜,但是應用預定義的泛型很簡單。例如,要想定義用來保存Apple對象的ArrayList,可以聲明

Fiddler抓包2_捕獲設置

from lang 請求 user src file ati 允許 iphone 1、Fiddler抓web網站請求 手動設置方法一:Tools--->WinINET Options--->連接--->局域網設置--->代理服務器勾選後“高級

TipsUE總結自己常用的UltraEdit使用技巧

快捷方式 導致 體驗 什麽是 換行 描述 習慣 目標 恢復 如果您問我每天都要打開的軟件是什麽,那毫無疑問是UltraEdit!作為一位DBA,每天都要寫各種腳本,尤其是在對具有超多行行的大文件進行精心編輯時,沒有一個好的文本編輯器是不成的。掐指一算,哇塞,自己使用Ultr

LeetCode數組類的題目提交記錄 2

targe result 有序 suppose middle size body some 遞歸 /***********************************************************************33. Search in Ro

DOM案例2註冊文本倒計時

元素 text disable als har www .org document www. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/h

MYSQL基礎入門學習2

保存 範圍 body 不同的 clas 排列 分類 入門 字節 1. 數據類型:指列、存儲過程參數、表達式和局部變量的數據特征,它決定了數據的存儲格式,代表了不同的信息類型 (1) 整型(按存儲範圍分類):TINYINT(1字節) SAMLLINT(2字節) MEDIUMI

HTML第一課——基礎知識普及2

成績 eqv apt awr .com UC S4B 搜索引擎 dib 關註公眾號:自動化測試實戰 img標簽 我們先看一下文檔結構: 這裏我們文件當前位置就是lesson.html,所以現在我們img屬性src給的值要進入imgs文件夾,所以我們可以用相

2信息的表示和處理

float 執行 單獨 因此 com alt 卡片 acl AC 1.現代計算機存儲和處理的信息都以二值信號表示。 2.機器為什麽要使用二進制進行存儲和處理? 答:二值信號能夠很容易的被表示、存儲、傳輸。例如: 可以表示為穿孔卡片上有洞和無洞、導線上的高壓和低壓,順逆