1. 程式人生 > >集合系列(一):集合框架概述

集合系列(一):集合框架概述

集合系列(一):集合框架概述

Java 集合是 Java API 用得最頻繁的一類,掌握 Java 集合的原理以及繼承結構非常有必要。總的來說,Java 容器可以劃分為 4 個部分:

  • List 集合
  • Set 集合
  • Queue 集合
  • Map 集合

除了上面 4 種集合之外,還有一個專門的工具類:

  • 工具類(Iterator 迭代器、Enumeration 列舉類、Arrays 和 Collections)

在開始聊具體的集合體系之前,我想先介紹一下 Collection 框架的基本類結構。因為無論是 List 集合、Set 集合還是 Map 集合都以這個為基礎。

  • 首先,最頂層的是 Collection 介面。

可以看到 Collection 介面定義了最最基本的集合操作,例如:判斷集合大小、判斷集合是否為空等。List、Set、Queue 都繼承了該介面。

  • 接著,AbstractCollection 也繼承了 Collection 介面。

從這個類名可以看出,其是一個抽象類。AbstractCollection 對 Collection 介面中一些通用的方法做了實現。例如:判斷是否為空的方法、判斷是否包含某個元素的方法等。

通過繼承 AbstractCollection 介面,可以少寫許多不必要的程式碼,這是程式碼抽象設計最常用的思想。AbstractCollection 是最為基礎的類,其他所有集合的實現都繼承了這個抽象類。

List 集合

List 集合儲存的是有序的資料集合,其資料結構特點是:讀取快,修改慢,適合於讀取多、寫入修改少的場景。List 集合的類繼承結構如下:

我們可以看到除了 Collection 和 AbstractCollection 之外,我們還有 List 介面和 AbstractList 抽象類。其中 List 介面是 List 集合的最上層抽象,其繼承了 Collection 介面,表示其實一個集合。而 AbstractList 則是 List 集合的抽象實現,實現了許多公用的操作。

整個 List 集合的實現可以分為紅、黃、綠三大塊。其中紅色部分是 List 集合的列表實現,綠色部分是 List 結合的連結串列實現,而 黃色部分則是 List 集合列表實現的執行緒安全版本。

列表實現

ArrayList 類是很常用的 List 實現,其底層是用陣列實現的。其讀取元素的時間複雜度是 O(1),修改寫入元素的時間複雜度是 O(N)。我們將會在下面的章節中詳細介紹,這裡不做深入。

列表安全實現

Vector 類也是很常用的 List 實現,其資料結構與 ArrayList 非常類似。但其與 ArrayList 的一個最大的不同是:Vector 是執行緒安全的,而 ArrayList 則不是執行緒安全的。

Stack 類則是在 Vector 的基礎上,又實現了一個雙向佇列。所以其除了是執行緒安全的之外,其還是一個先進後出的 List 實現。

最後我們總結一下,List 集合最為關鍵的幾個實現類是:

  • ArrayList:列表集合經典實現。
  • Vector:列表集合經典實現,執行緒安全,與 ArrayList 對應。
  • Stack:棧結構的經典實現,先進後出的資料結構。繼承了 Vector,執行緒安全。
  • LinkedList:連結串列結構的經典實現。

連結串列實現

LinkedList 是一個經典的連結串列實現。LinkedList 繼承了 AbstractSequentialList 抽象類。AbstractSequentialList 抽象類從字面上理解是抽象連續列表。這裡的重點是
sequential 這個詞,表示其資料結構是連續的(連結串列)。從其原始碼註釋也可以看出這個意思。

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).
(意譯)如果你想要實現一個連續儲存(連結串列)的 List,那麼這個抽象類可以讓你減少不少工足量。

其實從命名就可以看出,AbstractSequentialList 其實是連續列表(連結串列)的一個抽象實現。AbstractSequentialList 抽象類做了許多工作,使得後續的連結串列實現更加簡單。從 AbstractSequentialList 的註釋可以看到,如果要實現一個連結串列,那麼只需要實現 listIterator 方法和 size 方法就可以了。

Set 集合

Set 集合中儲存的元素是不重複的,但是其儲存順序是無序的。下面是 Set 集合的類繼承結構圖:

與 List 集合類似,都是一個 Set 介面繼承了 Collection 介面。一個 AbstractSet 抽象類實現了 Set 介面、繼承了 AbstractCollection 抽象類。這部分完全和 List 相同。

Set 集合的實現可以分為兩大塊,一塊是 Set 集合的有序實現(紅色部分),另一塊是 Set 集合的雜湊實現(黃色部分)。

有序實現(TreeSet)

  • SortedSet 介面繼承了 Set 介面,TreeSet 實現了 SortedSet。

我們知道 Set 集合中的元素是無序的,而 SortedSet 介面則是定義了有序 Set 集合的介面。而 TreeSet 則是 SortedSet 的具體實現。

雜湊實現(HashSet、LinkedHashSet)

HashSet 是 Set 介面的經典雜湊實現。但 Set 集合中的元素是無序的,為了維護 Set 集合的插入順序,人們創造出了 LinkedHashSet。LinkedHashSet 在 HashSet 的基礎上是用連結串列維護元素的插入順序。

到這裡我們總結一下 Set 集合的所有實現:

  • TreeSet:Set 集合的有序實現。
  • HashSet:Set 集合的雜湊實現。
  • LinkedHashSet:Set 集合的雜湊實現,維護了元素插入順序。

Queue 集合

佇列是一個特殊的線性表,其資料結構特點是先進先出。Queue 類結構體系如下圖所示:

首先,Queue 介面繼承了 Collection 介面。Queue 介面在擁有基本集合操作的基礎上,定義了佇列這種資料結構的基本操作。可以看到 offer、poll 等方法都是佇列獨有的操作。

接著,AbstractQueue 是對 Queue 介面的抽象實現。針對佇列這種資料結構,其新增、刪除元素的動作都不一樣。在 AbstractQueue 抽象類裡將佇列的基本操作都實現了一遍。例如 AbstractQueue 中的 add 方法就和 AbstractList 中的 add 方法有著不同的實現。

如上圖所示,Queue 的類結構整體可以分為黃色、紅色兩個部分。紅色部分是 Queue 介面的有序實現,有 PriorityQueue 這個實現類。黃色部分是 Deque(雙向佇列)的實現,有 LinkedList 和 ArrayDeque 兩個實現類。

有序實現

PriorityQueue 是 AbstractQueue 抽象類的具體實現。

PriorityQueue 表示優先順序佇列,其按照佇列元素的大小進行重新排序。當呼叫 peek() 或 pool() 方法取出佇列中頭部的元素時,並不是取出最先進入佇列的元素,而是取出佇列的最小元素。

雙向實現

  • 首先,我們會看到 Deque 介面。

Deque(double ended queue)是雙向佇列的意思,它能在頭部或尾部進行元素操作。

  • 最後,我們看到 LinkedList 和 ArrayDeque 都是 Deque 介面的具體實現。

LinkedList 我們之前說過了,是一個連結串列,但它還是一個雙向佇列。因此 LinkedList 具有 List 和 Queue 的雙重特性。ArrayDeque 則是一個雙向迴圈佇列,其底層是用陣列實現。更多內容,我們將在佇列章節講解。

最後我們總結 Queue 體系的幾個常見實現類:

  • PriorityQueue:優先順序佇列
  • LinkedList:雙向佇列實現
  • ArrayDeque:雙向迴圈佇列實現

Map 集合

Map 集合與 List、Set、Queue 有較大不同,其實類似於 key/value 的資料結構。

  • 首先,Map 介面是最頂層的介面。

與 List、Set、Queue 類似,Map 介面定義的是雜湊表資料結構的操作。例如我們常用的 put、get、keySet 等。

  • 接著,有 AbstractMap 抽象類。

和 List 等類似,AbstractMap 是 Map 介面的抽象實現。如上圖所示,Map 集合的整個類結構可以分為紅、黃、綠三塊。

雜湊實現

紅色部分可以看成是 Map 的雜湊實現。

  • AbstractMap 有具體的實現類 HashMap。

HashMap 是 AbstractMap 基於雜湊演算法的具體實現。

  • 接著,LinkedHashMap 和 WeakedHashMap 繼承了 HashMap。

LinkedHashMap 是 HashMap 的進一步實現,其用連結串列儲存了插入 HashMap 中的元素順序。WeakedHashMap 是 HashMap 的進一步實現,與 HashMap不同的是:WeakedHashMap 中的引用是弱引用,如果太久沒用,則會被自動回收。

有序實現

黃色部分可以看成是 Map 集合的有序實現。

  • 首先,SortedMap 介面繼承了 Map 介面。

與 Set 一樣,Map 中的元素是沒有順序的,SortedMap 就是有序 Map 的介面定義。

  • 接著,NavigableMap 繼承了 SortedMap 介面。

NavigableMap 介面定義了一些查詢邏輯,方便後續實現。

  • 最後,TreeMap 則是 NavigableMap 介面的具體實現。

其實 TreeMap 是基於紅黑樹的 Map 實現。

看到了這裡,Map 整個類結構看完了一半。而另外一半則是以 Dictionary 為主的實現(綠色部分)。但實際上 Dictionary 是老舊的 Map 實現,現在已經廢棄了。我們從原始碼的註釋中可以看到相關的提示。

NOTE: This class is obsolete(廢棄的).  New implementations should implement the Map interface, rather than extending this class.
這個類已經被廢棄,新的實現應該實現 Map 介面,而不是擴充套件這個類。

所以針對於 Dictionary 的實現,我們並不打算深入講解。

到這裡我們總結一下 Map 集合的所有實現類:

  • HashMap:Map 集合的經典雜湊實現。
  • LinkedHashMap:在 HashMap 的基礎上,增加了對插入元素的連結串列維護。
  • WeakedHashMap:在 HashMap 的基礎上,使強引用變為弱引用。
  • TreeMap:Map 集合的有序實現。

工具類

集合的工具類有:Iterator 迭代器、ListIterator 迭代器、Enumeration 列舉類、Arrays 和 Collections 類。

Iterator 迭代器

Iterator 迭代器是一個用來遍歷並選擇序列中的物件。Java 的 Iterator 只能單向移動。可以看到在 ArrayList、WeakHashMap 等集合類都實現了該介面,從而實現不同型別集合的遍歷。

ListIterator 迭代器

ListIterator 繼承了 Iterator 介面,所以其有更強大的功能,即它能夠實現雙向移動。但從其名字也可以看出,其只能適用於 List 集合的遍歷。

Enumeration 列舉類

它是 JDK 1.0引入的介面。作用和Iterator一樣,也是遍歷集合。但是Enumeration的功能要比Iterator少。Enumeration只能在Hashtable, Vector, Stack中使用。這種傳統介面已被迭代器取代,雖然 Enumeration 還未被遺棄,但在程式碼中已經被很少使用了。

官方也在文件中推薦使用 Iterator 介面來替代 Enumeration 介面。

Arrays

Java.util.Arrays類能方便地運算元組,它提供的所有方法都是靜態的。

Collections

java.util.Collections 是一個包含各種有關集合操作的靜態多型方法的工具類,服務於 Java 的 Collection 框架。

總結

我們花費了大量的篇幅講解了 List 集合、Set 集合、Map 集合、Queue 集合以及 Iterator 等工具類。我們對這集合的類結構進行了詳細的解析,從而更加了解他們之間的關係。

有時候我們會想,瞭解這麼多有啥用呢。我有個朋友只用了常見的 ArrayList、HashMap 就可以了啊。對於這個問題,我想分享幾個收穫。

第一,讓你更加熟悉類之間的差異。 如果我們只會用一兩個類,那麼我們就不知道在什麼時候用什麼類。例如:什麼時候用 HashMap,什麼時候用 Hashtable?Iterator 介面有什麼作用?JDK原始碼的命名有什麼特點?

第二,方便對原始碼進行擴充套件。 當我們深入研究了集合的實現之後,我們知道了原來 List 介面就是 List 這種資料型別的定義,而 AbstractList 是 List 的抽象實現。那麼如果我們要實現一個自定義的 List 結構,那麼我們就可以直接繼承 AbstractList 類,從而達到快速實現的目的。但如果你沒有深入研究呢?你或許只能從頭寫起,這樣得浪費多大的精力啊。你學會了這種方式,那麼對於你擴充套件 Spring 原始碼也是有很好的幫助的。

在接下來的文章裡,我們將深入介紹每一個集合的具體實現