java集合類總結

Java集合概述

Java提供的衆多集合類由兩大接口衍生而來: Collection 接口和 Map 接口。爲了更好的把握Java集合類的整體結構,我這裏先貼一個Java集合的整體類圖,以便大家對Java集合類有一個整體的印象。

Java集合類總結
乍一看這個圖很複雜,其實我們仔細梳理一下,這個圖還是非常清晰的。可以這麼看,在Java的集合類中,主要分爲 List 、 Map 、 Set 和 Queue 這四大類,這四大接口類下面,又根據使用場景分爲多個具體的子類。下面就一一進行總結。

Collection接口說明

從類圖上可以看到, Collection 接口作爲一個非常重要的基礎接口,所以我們有必要對 Collection 接口中的常用方法進行一下說明和總結:

add :向集合中添加單個元素
addAll :向集合中批量添加元素
clear :刪除集合中所有元素
contains :判斷集合是否包含某個元素
isEmpty :判斷集合是否爲空
iterator :返回一個集合迭代器;
remove :從集合中刪除單個元素
removeAll :從集合中批量刪除元素
retainAll :保留指定入參集合中的元素,刪除其它元素
size :獲取集合中元素個數
toArray :將集合轉換爲數組
Map接口說明

同樣的, Map 接口作爲非常重要的接口,也有必要對其中的一些重要方法進行一些說明:

clear :刪除所有元素
containsKey :判斷是否包含某個鍵
containsValue :判斷是否包含某個值
entrySet :將Map鍵值對以Map.Entry的形式放入Set集合中返回
get :返回key值所對應的對象
isEmpty :判斷是否爲空
keySet :返回所有鍵的Set集合,
put :向Map中添加單個元素
putAll :向Map中批量添加元素
remove :刪除Key所對應的對象
size :獲取Map中鍵值對的個數
values :返回所有值的集合
說完這兩大常用接口的常用方法,下面就對這兩大接口衍生出來的常用集合類進行說明和總結。

List

List 用於定義以列表形式存儲的集合, List 接口爲集合中的每個對象分配了一個索引,用來標記該對象在List中的位置,並可以通過索引定位到指定位置的對象。

在我們開發過程中, List 類的集合出鏡頻率非常高,對於 List 類的集合,我們需要知道常用的有 ArrayList 、 LinkedList 、 Vector 、 CopyOnWriteArrayList ,特別是 ArrayList 和 CopyOnWriteArrayList 這兩貨,更是頻繁出鏡。

ArrayList
通過名稱基本上就能看出來, ArrayList 基於數組實現的非線程安全的集合,在內部實現上,其維護了一個可變長度的對象數組,集合內所有對象存儲於這個數組中,並實現該數組長度的動態伸縮。知道了內部的實現原理,那對於 ArrayList 來說,就有以下幾個特性:
插入和刪除元素性能較差
索引元素性能非常高
涉及數組長度動態伸縮,影響性能
如果涉及到頻繁的插入和刪除元素, ArrayList 則不是最好的選擇。
LinkedList
LinkedList 基於鏈表實現的非線程安全的集合,在內部實現上,其實現了靜態類Node,集合中的每個對象都由一個Node保存,每個Node都擁有到自己的前一個和後一個Node引用。對於 LinkedList 來說,它具備以下特性:
LinkedList
Vector
基於數組實現的線程安全的集合。線程同步(方法被 synchronized 修飾),性能比 ArrayList 差。當併發量增多時,鎖競爭的問題嚴重,會導致性能下降。
CopyOnWriteArrayList
與 Vector 一樣, CopyOnWriteArrayList 也可以認爲是 ArrayList 的線程安全版,不同之處在於 CopyOnWriteArrayList 在寫操作時會先複製出一個副本,在新副本上執行寫操作,然後再修改引用。這種機制讓 CopyOnWriteArrayList 可以對讀操作不加鎖,這就使 CopyOnWriteArrayList 的讀效率遠高於Vector。 CopyOnWriteArrayList 的理念比較類似讀寫分離,適合讀多寫少的多線程場景。但要注意, CopyOnWriteArrayList 只能保證數據的最終一致性,並不能保證數據的實時一致性,如果一個寫操作正在進行中且並未完成,此時的讀操作無法保證能讀到這個寫操作的結果。
CopyOnWriteArrayList 寫時複製的集合,在執行寫操作(如:add,set,remove等)時,都會將原數組拷貝一份,然後在新數組上做修改操作。最後集合的引用指向新數組。 CopyOnWriteArrayList 和 Vector 都是線程安全的,不同的是:前者使用 ReentrantLock 類,後者使用 synchronized 關鍵字。 ReentrantLock 提供了更多的鎖投票機制,在鎖競爭的情況下能表現更佳的性能。就是它讓JVM能更快的調度線程,纔有更多的時間去執行線程。這就是爲什麼 CopyOnWriteArrayList 的性能在大併發量的情況下優於 Vector 的原因。
對於 CopyOnWriteArrayList 來說,非常適合高併發的讀操作(讀多寫少)的場景下使用。若寫的操作非常多,會頻繁複制容器,從而影響性能。
Map

Map 存儲的是鍵值對,它將key和value封裝至一個叫做Entry的對象中。每一個Map根據其自身的特點,都有不同的Entry實現,以對應Map的內部類形式出現。

根據我現在的開發情況來看, Map 比 List 類的集合更常用。對於 Map 類的集合有 HashMap、 HashTable 、 SortedMap 、 TreeMap 、 WeakHashMap 和 ConcurrentSkipListMap 。

HashMap
HashMap 的底層是基於 數組+鏈表+紅黑樹 (JDK1.8+)的方式實現的。 HashMap 將 Entry 對象存儲在一個數組中,並通過哈希表來實現對 Entry 的快速訪問。感覺這裏不放一張圖,就不能更好的理解 HashMap 的實現方式了:
Java集合類總結

通過上圖大家應該有一個整體的理解,我這裏也不會對 HashMap 的實現原理進行更進一步的剖析。對於 HashMap 的一些特性這裏進行列舉:
當儲存對象時,我們將鍵值對傳遞給put(key,value)方法時,它調用鍵對象key的hashCode()方法來計算hashcode,然後找到bucket位置,來儲存值對象value
hash表裏可以存儲元素的位置稱爲桶(bucket),如果通過key計算hash值發生衝突時,那麼將採用鏈表的形式,來存儲元素
HashMap的擴容操作是一項很耗時的任務,所以如果能估算Map的容量,最好給它一個默認初始值,避免進行多次擴容;當數量達到了16 * 0.75 = 12就需要將當前16的容量進行擴容,而擴容這個過程涉及到 rehash、複製數據等操作,所以非常消耗性能
允許使用 null 建和 null 值
非線程安全
HashTable
HashTable 是 HashMap 的線程安全版, Hashtable 的實現方法裏面都添加了 synchronized關鍵字來確保線程同步。對於 HashTable 這種上古的東西,在開發中不建議使用了,因爲現在已經提供了 ConcurrentHashMap 來使用。
ConcurrentHashMap
ConcurrentHashMap 是 HashMap 的線程安全版(自JDK1.5引入),提供比 Hashtable 更高效的併發性能。
HashTable 在進行讀寫操作時會鎖住整個Entry數組,這就導致數據越多性能越差。而 ConcurrentHashMap 使用分離鎖的思路解決併發性能,其將Entry數組拆分至16個Segment中,以哈希算法決定Entry應該存儲在哪個Segment。這樣就可以實現在寫操作時只對一個Segment加鎖,大幅提升了併發寫的性能。在進行讀操作時, ConcurrentHashMap 在絕大部分情況下都不需要加鎖,其Entry中的value是volatile的,這保證了value被修改時的線程可見性,無需加鎖便能實現線程安全的讀操作。
ConcurrentHashMap 採用了分段鎖技術,其中Segment繼承於ReentrantLock。不會像 HashTable 那樣不管是put還是get操作都需要做同步處理,理論上 ConcurrentHashMap 支持 CurrencyLevel (Segment數組數量)的線程併發。每當一個線程佔用鎖訪問一個Segment時,不會影響到其他的Segment。
Set

Set 用於存儲不含重複元素的集合,幾乎所有的Set實現都是基於同類型Map的。簡單地說,Set是閹割版的Map。每一個Set內都有一個同類型的Map實例( CopyOnWriteArraySet 除外,它內置的是 CopyOnWriteArrayList 實例),Set把元素作爲key存儲在自己的Map實例中,value則是一個空的Object。 Set 的常用實現包括 HashSet 、 TreeSet 和 ConcurrentSkipListSet ,由於實現原理和對應的Map是完全一致的,所以這裏就不再贅述。

在實際評審代碼中,發現開發人員很少用 Set 類型的集合,即使有存儲不含重複元素的場景,也都是使用 ArrayList 集合,然後結合着 contains 這種奇葩方式來實現。也就是說,一些基本功不紮實的開發人員,在腦海中就沒有 Set 集合的概念。抱着實現功能就OK的心態,管他代碼質量好不好,全憑 ArrayList 和 HashMap 闖天下。

Queue

Queue 用於模擬“隊列”這種數據結構(先進先出FIFO)。隊列的頭部保存着隊列中存放時間最長的元素,隊列的尾部保存着隊列中存放時間最短的元素。新元素插入到隊列的尾部。這種隊列基本都只是在小數據量的情況下使用,對於互聯網應用來說,基本都是在使用分佈式消息隊列中間件。從文章開頭的類圖中可以看出, Deque 接口繼承了 Queue 接口, Deque 接口代表一個“雙端隊列”,雙端隊列可以同時從兩端來添加、刪除元素,因此 Deque 的實現類既可以當成隊列使用、也可以當成棧使用。對於我們來說,常用的 Queue 實現類有 ArrayDeque 、 ConcurrentLinkedQueue 、 LinkedBlockingQueue 、 ArrayBlockingQueue 、 SynchronousQueue 、 PriorityQueue 和 PriorityBlockingQueue 。

ArrayDeque
是一個基於數組的雙端隊列,和 ArrayList 類似,它們的底層都採用一個動態的、可重分配的Object[]數組來存儲集合元素,當集合元素超出該數組的容量時,系統會在底層重新分配一個Object[]數組來存儲集合元素。
ConcurrentLinkedQueue
ConcurrentLinkedQueue 是基於鏈表實現的線程安全、無界非阻塞隊列,隊列中每個Node擁有到下一個Node的引用。它能夠保證入隊和出隊操作的原子性和一致性,但在遍歷和size()操作時只能保證數據的弱一致性。
LinkedBlockingQueue
與 ConcurrentLinkedQueue 不同, LinkedBlocklingQueue 是一種無界的阻塞隊列。所謂阻塞隊列,就是在入隊時如果隊列已滿,線程會被阻塞,直到隊列有空間供入隊再返回;同時在出隊時,如果隊列已空,線程也會被阻塞,直到隊列中有元素供出隊時再返回。 LinkedBlocklingQueue 同樣基於鏈表實現,其出隊和入隊操作都會使用ReentrantLock進行加鎖。所以本身是線程安全的,但同樣的,只能保證入隊和出隊操作的原子性和一致性,在遍歷時只能保證數據的弱一致性。
ArrayBlockingQueue
ArrayBlockingQueue 是一種有界的阻塞隊列,基於數組實現。其同步阻塞機制的實現與 LinkedBlocklingQueue 基本一致,區別僅在於前者的生產和消費使用同一個鎖,後者的生產和消費使用分離的兩個鎖。
SynchronousQueue
SynchronousQueue 算是JDK實現的隊列中比較奇葩的一個,它不能保存任何元素,size永遠是0,peek()永遠返回null。向其中插入元素的線程會阻塞,直到有另一個線程將這個元素取走,反之從其中取元素的線程也會阻塞,直到有另一個線程插入元素。這種實現機制非常適合傳遞性的場景。也就是說如果生產者線程需要及時確認到自己生產的任務已經被消費者線程取走後才能執行後續邏輯的場景下,適合使用 SynchronousQueue 。
PriorityQueue
PriorityQueue 是基於最小堆數據結構,可以在構造時指定 Comparator 或者按照自然順序排序。優先隊列有最大優先隊列和最小優先隊列,分別由最大堆和最小堆實現。 PriorityQueue是非阻塞隊列,也不是線程安全的。
PriorityBlockingQueue
PriorityBlockingQueue 實現原理同 PriorityQueue 一樣,但是 PriorityBlockingQueue 是阻塞隊列,同時也是線程安全的。
Deque 的實現類包括 LinkedList (前文已經總結過)、 ConcurrentLinkedDeque 和 LinkedBlockingDeque ,其實現機制與上面所述的 ConcurrentLinkedQueue 和 LinkedBlockingQueue 非常類似,此處不再贅述。

總結

這裏對Java中的一些常用集合類進行了大概原理性的總結,並沒有深入到源碼級別,如果深入到源碼級別,那就夠講一本書的了,而且花費的精力和時間也太大了,這裏就是淺嘗輒止,有個基本的瞭解即可。瞭解原理,對自己寫的代碼負責。

需要java學習路線圖的私信筆者“java”領取哦!另外喜歡這篇文章的可以給筆者點個贊,關注一下,每天都會分享Java相關文章!還有不定時的福利贈送,包括整理的學習資料,面試題,源碼等~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章