1. 程式人生 > 程式設計 >?史上最全的Java容器集合之Vector和LinkedList(原始碼解讀)

?史上最全的Java容器集合之Vector和LinkedList(原始碼解讀)

前言

文字已收錄至我的GitHub倉庫,歡迎Star:github.com/bin39232820…
種一棵樹最好的時間是十年前,其次是現在
我知道很多人不玩qq了,但是懷舊一下,歡迎加入六脈神劍Java菜鳥學習群,群聊號碼:549684836 鼓勵大家在技術的路上寫部落格

絮叨

前面2篇的基礎,大家還是好好學習一下,下面是連結
?史上最全的Java容器集合之入門
?史上最全的Java容器集合之基礎資料結構(手撕連結串列)
?史上最全的Java容器集合之ArrayList(原始碼解讀)
今天講Vector和LinkedList(順便提一下,如果是零基礎的不建議來,有過半年工作經驗的跟著我一起把這些過一遍的話,對你的幫助是非常大的)

Vector 原始碼分析

其實Vector要講的東西不多了,因為它和ArrayList的程式碼很像,就是再每個方法上加了鎖,如下圖

因為大部分和前面差不多,我來說說不同的點吧

看圖上面的 這個是Vetor和ArrayList不同的另一個點 它的增長因子是可以自己定義的。我們來看grow方法

這段程式碼是擴容程式碼,可以看如果定義了曾長因子就每次擴容增長因子,不然就是擴容2倍

其他的增刪改查,我就不說了,自己也沒細看,但是底層是陣列,所以大多數是相同了 只是加了一個synchronized鎖

這邊提一下其實ArrayList也可以變成執行緒安全

如果想要ArrayList實現同步,可以使用Collections的方法:List list=Collections.synchronizedList(new ArrayList(...));,就可以實現同步了

LinkedList原始碼分析

概念

  • LinkedList是基於連結串列實現的,所以先講解一下什麼是連結串列。連結串列原先是C/C++的概念,是一種線性的儲存結構,意思是將要儲存的資料存在一個儲存單元裡面,這個儲存單元裡面除了存放有待儲存的資料以外,還儲存有其下一個儲存單元的地址(下一個儲存單元的地址是必要的,有些儲存結構還存放有其前一個儲存單元的地址),每次查詢資料的時候,通過某個儲存單元中的下一個儲存單元的地址尋找其後面的那個儲存單元。
  • 理解:
    • LinkedList是一個雙向連結串列
    • 也就是說list中的每個元素,在儲存自身值之外,還額外儲存了其前一個和後一個元素的地址,所以 也就可以很方便地根據當前元素獲取到其前後的元素
    • 連結串列的尾部元素的後一個節點是連結串列的頭節點;而連結串列的頭結點前一個節點則是則是連結串列的尾節點(是不是有點像貪吃蛇最後 頭吃到自己尾巴的樣子,腦補下)
    • 既然是一個雙向連結串列,那麼必然有一個基本的儲存單元,讓我們來看LinkedList的最基礎的儲存單元。

對於單項鍊表 自己手撕了一個,有興趣的可以去看看
?史上最全的Java容器集合之基礎資料結構(手撕連結串列)

繼承結構和層次關係

從這個圖來對 和底層是陣列結構的2個集合的區別 少實現了一個隨機訪問的RandomAccess介面 多實現了一個Deque介面 所以linkedList並不具備隨機訪問的功能,它也必須一個個去遍歷,結論是: ArrayList用for迴圈遍歷比iterator迭代器遍歷快,LinkedList用iterator迭代器遍歷比for迴圈遍歷

還有可以看出LinkedList與ArrayList的另外不同之處,ArrayList直接繼承自AbstractList,而LinkedList繼承自AbstractSequentialList,然後再繼承自AbstractList。另外,LinkedList還實現了Dequeu介面,雙端佇列。

Deque雙端佇列

Deque 雙端佇列,這個之前沒講過,唉 一個個坑,自己填吧

雙端佇列(deque,全名double-ended queue),是一種具有佇列和棧的性質的資料結構。佇列是一個有序列表,可以用陣列或是連結串列來實現。

雙端佇列中的元素可以從兩端彈出,其限定插入和刪除操作在表的兩端進行。雙端佇列可以在佇列任意一端入隊和出隊,

遵循先入先出的原則:

  • 先存入佇列的資料,要先取出。
  • 後存入的要後取出

來看看佇列的方法吧

  • add和offer 都表示插入 區別是一個失敗會報錯,一個失敗返回false
  • remove 和 poll 都表示刪除 都是刪除成功返回隊首元素 當佇列為空時它會丟擲異常。remove失敗的話會報錯 poll返回null
  • element peek 返回隊首元素,但是不刪除隊首元素,element 當佇列為空時它會丟擲異常。peek返回null

總結一下 其實佇列很簡單,特別像這種簡單 只有三個操作 加入 刪除 檢視 且全部是針對隊首的操作。

接下來我們看看deque吧

有點多 哈哈 其實操作也差不多,佇列就是隻能操作隊首,雙向佇列就是可以操作隊首和隊尾

手撕一個簡單的佇列

我們知道佇列它的底層可以是陣列或者是連結串列, 我們今天就用陣列來實現一個簡單的佇列

package com.atguigu.ct.producer.controller;

/**
 * 六脈神劍
 * 1.使用陣列實現佇列功能,使用int陣列儲存資料特點:先進先出,後進後出
 */

public class QueueTest1 {
    public static void main(String[] args){

        //測試佇列
        System.out.println("測試佇列");
        Queue queue = new Queue();
        queue.in("六脈神劍");
        queue.in("七賣神劍");
        queue.in("八面玲瓏");
        System.out.println(queue.out());
        System.out.println(queue.out());


    }
}

//使用陣列定義一個佇列
class Queue {

    String[] a = new String[5];
    int i = 1; //陣列下標

    //入隊
    public void in(String m){
        a[i++] = m;
    }

    //出隊
    public String out(){
        int index = 0;
        String temp = a[1];
        for(int j=1;j<i;j++){
            a[j-1] = a[j];
            index++;
        }
        i = index;
        return temp;
    }
}

複製程式碼

結果

測試佇列
六脈神劍
七賣神劍

Process finished with exit code 0
複製程式碼

其實很簡單 就是每次出來的時候 把所有的元素的下標往前移一位。佇列深入,這邊就不深入了,靠各位大佬自己了,我也是黃婆賣瓜。接下來我們講LinkedList

LinkedList 常量

三個常量 一個表示這個集合的大小,一個表示佇列的元素的前一個元素,一個表示佇列元素的後一個元素

來看一個Node 元素 它要存三個資料,一個是自己本身,一個是它的前驅,一個是它的後繼

構造方法

 LinkedList 有兩個建構函式,第一個是預設的空的建構函式,第二個是將已有元素的集合Collection 的例項新增到 LinkedList 中,呼叫的是 addAll() 方法,這個方法下面我們會介紹。

  注意:LinkedList 是沒有初始化連結串列大小的建構函式,因為連結串列不像陣列,一個定義好的陣列是必須要有確定的大小,然後去分配記憶體空間,而連結串列不一樣,它沒有確定的大小,通過指標的移動來指向下一個記憶體地址的分配。

新增元素

  • addFirst(E e)
    • 新增元素到連結串列的頭部 只需要替換連結串列頭部的後繼指標就好了
  • addLast(E e)和add(E e)
    •  將指定元素新增到連結串列尾
  • add(int index,E element)
    • 講元素拆入到指定的位置,這個要先找到這個元素的前後的元素,然後再修改。
  • addAll(Collection<? extends E> c)
    • 按照指定集合的​​迭代器返回的順序,將指定集合中的所有元素追加到此列表的末尾

刪除元素

刪除元素和新增元素一樣,也是通過更改指向上一個節點和指向下一個節點的引用即可。

  • remove()和removeFirst()
    • 從此列表中移除並返回第一個元素
  • removeLast()
    • 從該列表中刪除並返回最後一個元素
  • remove(int index)
    • 刪除此列表中指定位置的元素
  • remove(Object o)
    • 如果存在,則從該列表中刪除指定元素的第一次出現,需要注意的是,這個是刪除第一個出現的,並不是刪除所有的這個元素

修改元素

通過呼叫 set(int index,E element) 方法,用指定的元素替換此列表中指定位置的元素。

  這裡主要是通過 node(index) 方法獲取指定索引位置的節點,然後修改此節點位置的元素即可。

查詢元素

因為截圖截不全我就沒截圖了,大家可以對著原始碼看,具體的實現確實也不難,前面我們手撕連結串列大部分也實現了

  • getFirst()
    • 返回此列表中的最後一個元素
  • getLast()
    • 返回此列表中的最後一個元素
  • get(int index)
    •  返回指定索引處的元素
  • indexOf(Object o)
    •  返回此列表中指定元素第一次出現的索引,如果此列表不包含元素,則返回-1。

遍歷集合

普通 for 迴圈

程式碼很簡單,我們就利用 LinkedList 的 get(int index) 方法,遍歷出所有的元素。  但是需要注意的是, get(int index) 方法每次都要遍歷該索引之前的所有元素,這句話這麼理解:比如上面的一個 LinkedList 集合,我放入了 A,B,C,D是個元素。

  • 總共需要四次遍歷:
  • 第一次遍歷列印 A:只需遍歷一次。
  • 第二次遍歷列印 B:需要先找到 A,然後再找到 B 列印。
  • 第三次遍歷列印 C:需要先找到 A,然後找到 B,最後找到 C 列印。
  • 第四次遍歷列印 D:需要先找到 A,然後找到 B,然後找到 C,最後找到 D。  
  • 這樣如果集合元素很多,越查詢到後面(當然此處的get方法進行了優化,查詢前半部分從前面開始遍歷,查詢後半部分從後面開始遍歷,但是需要的時間還是很多)花費的時間越多。那麼如何改進呢?

迭代器 這個比較適合 迭代器的另一種形式就是使用 foreach 迴圈,底層實現也是使用的迭代器,這裡我們就不做介紹了

總結

  • List繼承了Collection,是有序的列表,可重複。
  • 實現類有ArrayList、LinkedList、Vector、Stack等
    • ArrayList是基於陣列實現的,是一個陣列佇列。可以動態的增加容量!,執行緒不安全。基於陣列所以查快,增刪慢,因為如果要刪除的話,它後面的元素就要重新改版索引。
    • LinkedList是基於連結串列實現的,是一個雙向迴圈列表。可以被當做堆疊使用!,它的查慢,每次查都要遍歷整個集合,但是它的增刪快,特別是再頭尾新增,特別的快。
    • Vector是基於陣列實現的,是一個向量佇列,是執行緒安全的!基本差不多和ArrayList差不多,但是它是執行緒安全的,意味著效能沒有那麼好。

版本說明

  • 這裡的原始碼是JDK8版本,不同版本可能會有所差異,但是基本原理都是一樣的。

結尾

List的三個實現,講完了,是不是感覺也不是很難呢?博主跟著學,發現以前只是用,但是現在確實熟悉很多了。下節開始講Map,因為Set的底層是基於Map,它放最後

因為博主也是一個開發萌新 我也是一邊學一邊寫 我有個目標就是一週 二到三篇 希望能堅持個一年吧 希望各位大佬多提意見,讓我多學習,一起進步。

日常求贊

好了各位,以上就是這篇文章的全部內容了,能看到這裡的人呀,都是人才

創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見

六脈神劍 | 文 【原創】如果本篇部落格有任何錯誤,請批評指教,不勝感激 !