Week 2 - Java 容器 - 詳細剖析 List 之 ArrayList, Vector, LinkedList

前言

學習狀況記錄java

  • 時間:week 2
  • SMART子目標 :Java 容器

記錄在學習Java容器 知識點中,關於List的須要重點記錄的知識點。git

知識點概覽:github

  • ArrayList 與 LinkedList對比
  • ArrayList 中的 RandomAccess 接口 是什麼?
  • LinkedList 中的 Deque 接口 是什麼?
  • 老調常談 之 ArrayList 擴容機制
  • ArrayList 與 Vector 對比

ArrayList 與 LinkedList對比

  1. 底層數據結構:面試

    • ArrayList 底層使用的Object數組,默認大小 10

      **c#

    • LinkedList 底層使用的是雙向鏈表數據結構(JDK1.6以前爲循環鏈表,JDK1.7取消了循環。注意雙向鏈表和雙向循環鏈表的區別)。LinkedList 包含了3個重要的成員:sizefirstlastsize是雙向鏈表中節點的個數,firstlast分別指向第一個和最後一個節點的引用。

  2. 插入和刪除是否受元素位置的影響:數組

    • 因爲底層實現的影響,ArrayList 採用數組存儲,因此插入和刪除元素的時間複雜度受元素位置的影響。好比:執行add(E e)方法的時候, ArrayList 會默認在將指定的元素追加到此列表的末尾,這種狀況時間複雜度就是O(1)。可是若是要在指定位置 i 插入和刪除元素的話(add(int index, E element))時間複雜度就爲 O(n-i)。由於在進行上述操做的時候集合中第 i 和第 i 個元素以後的(n-i)個元素都要執行向後位/向前移一位的操做。實際就是近似O(n)
    • 而LinkedList 採用鏈表存儲,因此插入,刪除元素時間複雜度不受元素位置的影響,都是近似 O(1)
  3. 是否支持快速隨機訪問:這個也是由底層實現決定的,LinkedList 不支持高效的隨機元素訪問,而 ArrayList 支持。快速隨機訪問就是經過元素的序號快速獲取元素對象(對應於get(int index)方法)。
  4. 內存空間佔用:ArrayList的空間浪費主要體如今在list列表的結尾會預留必定的容量空間,而LinkedList的空間花費則體如今它的每個元素都須要消耗比ArrayList更多的空間(由於要存放prev 指針和next 指針以及數據)。

ArrayList 中的 RandomAccess 接口 是什麼?

前面的圖中咱們能夠看到ArrayList 繼承了三個接口,後面兩個都是比較熟悉的,分別是標識對象可複製和可序列化。安全

那麼RandomAccess 接口表明什麼呢?數據結構

前面的關於ArrayList 與 LinkedList 的對比當中,有一點就是,app

是否支持快速隨機訪問:這個也是由底層實現決定的,LinkedList 不支持高效的隨機元素訪問,而 ArrayList 支持。 快速隨機訪問就是經過元素的序號快速獲取元素對象(對應於 get(int index)方法)。

RandomAccess 接口 就是用來 標識該類支持快速隨機訪問。 查看源碼能夠發現,這個接口內部沒有任何的定義。僅僅是起標識做用。dom

好比在Collections.binarySearch()方法中,

實現了RandomAccess接口的List使用索引遍歷,而未實現RandomAccess接口的List使用迭代器遍歷。

總結一句話:實現RandomAccess接口的List能夠經過for循環來遍歷數據比使用iterator遍歷數據更高效,未實現RandomAccess接口的List能夠經過iterator遍歷數據比使用for循環來遍歷數據更高效。固然對於ArrayList來講,for循環遍歷和Iterator方式遍歷差距不大,可是對於LinkedList這種沒有實現的來講,兩種遍歷方式效率差距就有點大了。這主要是由於底層實現不一樣。

LinkedList 中的 Deque 接口是什麼?

與 ArrayList 相對應的,LinkedList 中也有一個值得好好研究的接口,那就是Deque 接口。

Deque - double-ended queue,中文名爲雙端隊列。

咱們都知道 Queue 是一個隊列,遵循 FIFO 準則,咱們也知道 Stack 是一個棧結構,遵循 FILO 準則。 而Deque 這個雙端隊列就厲害了,它既能夠實現棧的操做,也能夠實現隊列的操做,換句話說,實現了這個接口的類,既能夠做爲棧使用也能夠做爲隊列使用

如何做爲隊列使用呢? Deque 實現了 Queue,因此 Queue 全部的方法 Deque 都有,下面比較的是Deque區別 Queue 的方法:

Queue Deque
add(e) addLast()
offer(e) offerLast()
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()

如何做爲棧使用呢? 下面咱們來看看下雙端隊列做爲棧 Stack使用的時候方法對應關係。

Stack Deque
push(e) addFirst(e)
pop() removeFirst()
peek() peekFirst()

由於篇幅有限,具體實現源碼就不帶你們去分析了。

引一篇好文:搞懂 Java LinkedList 源碼

老調常談 之 ArrayList 擴容機制

這是一個很頻繁的面試點,故記錄一下。

如下是源碼部分。

  1. add()方法開始入手
  2. ensureCapacityInternal(size + 1) 確認當前數組能否容納 size + 1 個元素,若是不夠進行擴容
  3. grow(minCapacity) 這就是具體擴容的邏輯

    • 新容量的大小爲 oldCapacity + (oldCapacity >> 1),也就是舊容量的1.5
    • 擴容操做 須要 調用 Arrays.copyOf()這個方法,把原數組整個複製到新數組中,這個操做代價很高,因此 不少地方包括阿里開發手冊上也會建議 在集合初始化的時候就指定好大概的容量大小,減小擴容的次數

ArrayList 與 Vector 對比

ArrayList 與 Vector 的底層實現都是 Object 數組,因此二者使用和特性上很是相似。

不一樣的是,

  • Vector 是線程安全的,內部使用了synchronized 進行同步。這致使了 Vector 性能很是很差。相比較的話,推薦用ArrayList,而後本身控制同步。
  • Vector 每次擴容都是2 倍大小,而不是1.5

若是是想要達到線程安全的目的,Vector 有其餘的替代方案:

  • 使用 Collections.synchronizedList() 獲得一個線程安全的ArrayList(這類的Collections.synchronized*() 就是一層Wrapper,看源碼就知道了)
  • 也可使用J.U.C中的 CopyOnWriteArrayList 讀寫分離,後面的內容會有詳細介紹

參考

  1. 搞懂 Java LinkedList 源碼
  2. 《碼出高效》
  3. 《瘋狂Java講義》
  4. github cs-note
  5. java guide
相關文章
相關標籤/搜索