JDK提供了大量優秀的集合實現供開發者使用,合格的程序員必需要可以經過功能場景和性能需求選用最合適的集合,這就要求開發者必須熟悉Java的經常使用集合類。本文將就Java Collections Framework中經常使用的集合及其特色、適用場景、實現原理進行介紹,供學習者參考。固然,要真正深刻理解Java的集合實現,仍是要推薦去閱讀JDK的源碼。node
Java提供的衆多集合類由兩大接口衍生而來:Collection接口和Map接口程序員
Collection接口算法
Collection接口定義了一個包含一批對象的集合。接口的主要方法包括:數組
size() - 集合內的對象數量 add(E)/addAll(Collection) - 向集合內添加單個/批量對象 remove(Object)/removeAll(Collection) - 從集合內刪除單個/批量對象 contains(Object)/containsAll(Collection) - 判斷集合中是否存在某個/某些對象 toArray() - 返回包含集合內全部對象的數組 等安全
Map接口多線程
Map接口在Collection的基礎上,爲其中的每一個對象指定了一個key,並使用Entry保存每一個key-value對,以實現經過key快速定位到對象(value)。Map接口的主要方法包括:架構
size() - 集合內的對象數量 put(K,V)/putAll(Map) - 向Map內添加單個/批量對象 get(K) - 返回Key對應的對象 remove(K) - 刪除Key對應的對象 keySet() - 返回包含Map中全部key的Set values() - 返回包含Map中全部value的Collection entrySet() - 返回包含Map中全部key-value對的EntrySet containsKey(K)/containsValue(V) - 判斷Map中是否存在指定key/value 等併發
在瞭解了Collection和Map兩大接口以後,咱們再來看一下這兩個接口衍生出來的經常使用集合類:性能
List類集合學習
圖片.png
List接口繼承自Collection,用於定義以列表形式存儲的集合,List接口爲集合中的每一個對象分配了一個索引(index),標記該對象在List中的位置,並能夠經過index定位到指定位置的對象。
List在Collection基礎上增長的主要方法包括:
get(int) - 返回指定index位置上的對象 add(E)/add(int, E) - 在List末尾/指定index位置上插入一個對象 set(int, E) - 替換置於List指定index位置上的對象 indexOf(Object) - 返回指定對象在List中的index位置 subList(int,int) - 返回指定起始index到終止index的子List對象 等
List接口的經常使用實現類:
ArrayList
ArrayList基於數組來實現集合的功能,其內部維護了一個可變長的對象數組,集合內全部對象存儲於這個數組中,並實現該數組長度的動態伸縮
ArrayList使用數組拷貝來實現指定位置的插入和刪除:
插入:
圖片.png
刪除:
圖片.png
LinkedList
LinkedList基於鏈表來實現集合的功能,其實現了靜態類Node,集合中的每一個對象都由一個Node保存,每一個Node都擁有到本身的前一個和後一個Node的引用
LinkedList追加元素的過程示例:
圖片.png
ArrayList vs LinkedList
ArrayList的隨機訪問更高,基於數組實現的ArrayList可直接定位到目標對象,而LinkedList須要從頭Node或尾Node開始向後/向前遍歷若干次才能定位到目標對象 LinkedList在頭/尾節點執行插入/刪除操做的效率比ArrayList要高 因爲ArrayList每次擴容的容量是當前的1.5倍,因此LinkedList所佔的內存空間要更小一些 兩者的遍歷效率接近,但須要注意,遍歷LinkedList時應用iterator方式,不要用get(int)方式,不然效率會很低 Vector
Vector和ArrayList很像,都是基於數組實現的集合,它和ArrayList的主要區別在於
Vector是線程安全的,而ArrayList不是 因爲Vector中的方法基本都是synchronized的,其性能低於ArrayList Vector能夠定義數組長度擴容的因子,ArrayList不能 CopyOnWriteArrayList
與 Vector同樣,CopyOnWriteArrayList也能夠認爲是ArrayList的線程安全版,不一樣之處在於 CopyOnWriteArrayList在寫操做時會先複製出一個副本,在新副本上執行寫操做,而後再修改引用。這種機制讓 CopyOnWriteArrayList能夠對讀操做不加鎖,這就使CopyOnWriteArrayList的讀效率遠高於Vector。 CopyOnWriteArrayList的理念比較相似讀寫分離,適合讀多寫少的多線程場景。但要注意,CopyOnWriteArrayList只能保證數據的最終一致性,並不能保證數據的實時一致性,若是一個寫操做正在進行中且並未完成,此時的讀操做沒法保證能讀到這個寫操做的結果。
Vector vs CopyOnWriteArrayList
兩者均是線程安全的、基於數組實現的List Vector是【絕對】線程安全的,CopyOnWriteArrayList只能保證讀線程會讀到【已完成】的寫結果,但沒法像Vector同樣實現讀操做的【等待寫操做完成後再讀最新值】的能力 CopyOnWriteArrayList讀性能遠高於Vector,併發線程越多優點越明顯 CopyOnWriteArrayList佔用更多的內存空間 Map類集合
圖片.png
Map將key和value封裝至一個叫作Entry的對象中,Map中存儲的元素實際是Entry。只有在keySet()和values()方法被調用時,Map纔會將keySet和values對象實例化。
每個Map根據其自身特色,都有不一樣的Entry實現,以對應Map的內部類形式出現。
前文已經對Map接口的基本特色進行過描述,咱們直接來看一下Map接口的經常使用實現類
HashMap
HashMap將Entry對象存儲在一個數組中,並經過哈希表來實現對Entry的快速訪問:
圖片.png
由每一個Entry中的key的哈希值決定該Entry在數組中的位置。以這種特性可以實現經過key快速查找到Entry,從而得到該key對應的value。在不發生哈希衝突的前提下,查找的時間複雜度是O(1)。
若是兩個不一樣的key計算出的index是同樣的,就會發生兩個不一樣的key都對應到數組中同一個位置的狀況,也就是所謂的哈希衝突。HashMap處理哈 希衝突的方法是拉鍊法,也就是說數組中每一個位置保存的實際是一個Entry鏈表,鏈表中每一個Entry都擁有指向鏈表中後一個Entry的引用。在發生哈希衝突時,將衝突的Entry追加至鏈表的頭部。當HashMap在尋址時發現某個key對應的數組index上有多個Entry,便會遍歷該位置上的 Entry鏈表,直到找到目標的Entry。
圖片.png
HashMap的Entry類:
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; } HashMap因爲其快速尋址的特色,能夠說是最常常被使用的Map實現類
Hashtable
Hashtable 能夠說是HashMap的前身(Hashtable自JDK1.0就存在,而HashMap乃至整個Map接口都是JDK1.2引入的新特性),其實現思 路與HashMap幾乎徹底同樣,都是經過數組存儲Entry,以key的哈希值計算Entry在數組中的index,用拉鍊法解決哈希衝突。兩者最大的不一樣在於,Hashtable是線程安全的,其提供的方法幾乎都是同步的。
ConcurrentHashMap
ConcurrentHashMap是HashMap的線程安全版(自JDK1.5引入),提供比Hashtable更高效的併發性能。
圖片.png
Hashtable 在進行讀寫操做時會鎖住整個Entry數組,這就致使數據越多性能越差。而ConcurrentHashMap使用分離鎖的思路解決併發性能,其將 Entry數組拆分至16個Segment中,以哈希算法決定Entry應該存儲在哪一個Segment。這樣就能夠實如今寫操做時只對一個Segment 加鎖,大幅提高了併發寫的性能。
在進行讀操做時,ConcurrentHashMap在絕大部分狀況下都不須要加鎖,其Entry中的value是volatile的,這保證了value被修改時的線程可見性,無需加鎖便能實現線程安全的讀操做。
ConcurrentHashMap的HashEntry類:
static final class HashEntry<K,V> { final int hash; final K key; volatile V value; volatile HashEntry<K,V> next; } 可是魚與熊掌不可兼得,ConcurrentHashMap的高性能是有代價的(不然Hashtable就沒有存在價值了),那就是它不能保證讀操做的絕對 一致性。ConcurrentHashMap保證讀操做能獲取到已存在Entry的value的最新值,同時也能保證讀操做可獲取到已完成的寫操做的內容,但若是寫操做是在建立一個新的Entry,那麼在寫操做沒有完成時,讀操做是有可能獲取不到這個Entry的。
HashMap vs Hashtable vs ConcurrentHashMap
三者在數據存儲層面的機制原理基本一致 HashMap不是線程安全的,多線程環境下除了不能保證數據一致性以外,還有可能在rehash階段引起Entry鏈表成環,致使死循環 Hashtable是線程安全的,能保證絕對的數據一致性,但性能是問題,併發線程越多,性能越差 ConcurrentHashMap 也是線程安全的,使用分離鎖和volatile等方法極大地提高了讀寫性能,同時也能保證在絕大部分狀況下的數據一致性。但其不能保證絕對的數據一致性, 在一個線程向Map中加入Entry的操做沒有徹底完成以前,其餘線程有可能讀不到新加入的Entry LinkedHashMap
LinkedHashMap與HashMap很是相似,惟一的不一樣在於前者的Entry在HashMap.Entry的基礎上增長了到前一個插入和後一個插入的Entry的引用,以實現可以按Entry的插入順序進行遍歷。
圖片.png
TreeMap
TreeMap是基於紅黑樹實現的Map結構,其Entry類擁有到左/右葉子節點和父節點的引用,同時還記錄了本身的顏色:
static final class Entry<K,V> implements Map.Entry<K,V> { K key; V value; Entry<K,V> left = null; Entry<K,V> right = null; Entry<K,V> parent; boolean color = BLACK; } 紅黑樹實際是一種算法複雜但高效的平衡二叉樹,具有二叉樹的基本性質,即任何節點的值大於其左葉子節點,小於其右葉子節點,利用這種特性,TreeMap可以實現Entry的排序和快速查找。
關於紅黑樹的具體介紹,能夠參考這篇文章,很是詳細:blog.csdn.net/chenssy/art…
TreeMap的Entry是有序的,因此提供了一系列方便的功能,好比獲取以升序或降序排列的KeySet(EntrySet)、獲取在指定key(Entry)以前/以後的key(Entry)等等。適合須要對key進行有序操做的場景。
ConcurrentSkipListMap
ConcurrentSkipListMap一樣可以提供有序的Entry排列,但其實現原理與TreeMap不一樣,是基於跳錶(SkipList)的:
圖片.png
如上圖所示,ConcurrentSkipListMap由一個多級鏈表實現,底層鏈上擁有全部元素,逐級上升的過程當中每一個鏈的元素數遞減。在查找時從頂層鏈出發,按先右後下的優先級進行查找,從而實現快速尋址。
static class Index<K,V> { final Node<K,V> node; final Index<K,V> down;//下引用 volatile Index<K,V> right;//右引用 } 與TreeMap不一樣,ConcurrentSkipListMap在進行插入、刪除等操做時,只須要修改影響到的節點的右引用,而右引用又是volatile的,因此ConcurrentSkipListMap是線程安全的。但ConcurrentSkipListMap與ConcurrentHashMap同樣,不能保證數據的絕對一致性,在某些狀況下有可能沒法讀到正在被插入的數據。
TreeMap vs ConcurrentSkipListMap
兩者都可以提供有序的Entry集合 兩者的性能相近,查找時間複雜度都是O(logN) ConcurrentSkipListMap會佔用更多的內存空間 ConcurrentSkipListMap是線程安全的,TreeMap不是 Set類集合 Set 接口繼承Collection,用於存儲不含重複元素的集合。幾乎全部的Set實現都是基於同類型Map的,簡單地說,Set是閹割版的Map。每個Set內都有一個同類型的Map實例(CopyOnWriteArraySet除外,它內置的是CopyOnWriteArrayList實例),Set把元素做爲key存儲在本身的Map實例中,value則是一個空的Object。Set的經常使用實現也包括 HashSet、TreeSet、ConcurrentSkipListSet等,原理和對應的Map實現徹底一致,此處再也不贅述。
圖片.png
Queue/Deque類集合
圖片.png
Queue和Deque接口繼承Collection接口,實現FIFO(先進先出)的集合。兩者的區別在於,Queue只能在隊尾入隊,隊頭出隊,而Deque接口則在隊頭和隊尾均可以執行出/入隊操做
Queue接口經常使用方法:
add(E)/offer(E):入隊,即向隊尾追加元素,兩者的區別在於若是隊列是有界的,add方法在隊列已滿的狀況下會拋出IllegalStateException,而offer方法只會返回false remove()/poll():出隊,即從隊頭移除1個元素,兩者的區別在於若是隊列是空的,remove方法會拋出NoSuchElementException,而poll只會返回null element()/peek():查看隊頭元素,兩者的區別在於若是隊列是空的,element方法會拋出NoSuchElementException,而peek只會返回null Deque接口經常使用方法:
addFirst(E) / addLast(E) / offerFirst(E) / offerLast(E) removeFirst() / removeLast() / pollFirst() / pollLast() getFirst() / getLast() / peekFirst() / peekLast() removeFirstOccurrence(Object) / removeLastOccurrence(Object) Queue接口的經常使用實現類:
ConcurrentLinkedQueue
ConcurrentLinkedQueue是基於鏈表實現的隊列,隊列中每一個Node擁有到下一個Node的引用:
private static class Node { volatile E item; volatile Node next; } 因爲Node類的成員都是volatile的,因此ConcurrentLinkedQueue天然是線程安全的。可以保證入隊和出隊操做的原子性和一致性,但在遍歷和size()操做時只能保證數據的弱一致性。
LinkedBlockingQueue
與ConcurrentLinkedQueue不一樣,LinkedBlocklingQueue是一種無界的阻塞隊列。所謂阻塞隊列,就是在入隊時若是隊列已滿,線程會被阻塞,直到隊列有空間供入隊再返回;同時在出隊時,若是隊列已空,線程也會被阻塞,直到隊列中有元素供出隊時再返回。LinkedBlocklingQueue一樣基於鏈表實現,其出隊和入隊操做都會使用ReentrantLock進行加鎖。因此自己是線程安全的,但一樣的,只能保證入隊和出隊操做的原子性和一致性,在遍歷時只能保證數據的弱一致性。
ArrayBlockingQueue
ArrayBlockingQueue是一種有界的阻塞隊列,基於數組實現。其同步阻塞機制的實現與LinkedBlocklingQueue基本一致,區別僅在於前者的生產和消費使用同一個鎖,後者的生產和消費使用分離的兩個鎖。
ConcurrentLinkedQueue vsLinkedBlocklingQueue vs ArrayBlockingQueue
ConcurrentLinkedQueue是非阻塞隊列,其餘二者爲阻塞隊列 三者都是線程安全的 LinkedBlocklingQueue是無界的,適合實現不限長度的隊列, ArrayBlockingQueue適合實現定長的隊列 SynchronousQueue
SynchronousQueue算是JDK實現的隊列中比較奇葩的一個,它不能保存任何元素,size永遠是0,peek()永遠返回null。向其中插入元素的線程會阻塞,直到有另外一個線程將這個元素取走,反之從其中取元素的線程也會阻塞,直到有另外一個線程插入元素。
這種實現機制很是適合傳遞性的場景。也就是說若是生產者線程須要及時確認到本身生產的任務已經被消費者線程取走後才能執行後續邏輯的場景下,適合使用SynchronousQueue。
PriorityQueue & PriorityBlockingQueue
這兩種Queue並非FIFO隊列,而是根據元素的優先級進行排序,保證最小的元素最早出隊,也能夠在構造隊列時傳入Comparator實例,這樣PriorityQueue就會按照Comparator實例的要求對元素進行排序。
PriorityQueue是非阻塞隊列,也不是線程安全的,PriorityBlockingQueue是阻塞隊列,同時也是線程安全的。
Deque 的實現類包括LinkedList(前文已描述過)、ConcurrentLinkedDeque、LinkedBlockingDeque,其實現機制與前文所述的ConcurrentLinkedQueue和LinkedBlockingQueue很是相似,此處再也不贅述
最後,對本文中描述的經常使用集合實現類作一個簡單總結:
喜歡的點點關注,點點贊。 對Java技術,架構技術感興趣的同窗,歡迎加QQ羣668041364?,一塊兒學習,相互討論。
羣內已經有小夥伴將知識體系整理好(源碼,筆記,PPT,學習視頻),歡迎加羣領取。