Java集合的小抄 Java初學者必備

List
ArrayList

以數組實現。節約空間,但數組有容量限制。超出限制時會增長50%容量,用System.arraycopy()複製到新的數組,所以最好能給出數組大小的預估值。默認第一次插入元素時建立大小爲10的數組。
按數組下標訪問元素–get(i)/set(i,e) 的性能很高,這是數組的基本優點。
直接在數組末尾加入元素–add(e)的性能也高,但若是按下標插入、刪除元素–add(i,e), remove(i), remove(e),則要用System.arraycopy()來移動部分受影響的元素,性能就變差了,這是基本劣勢。

LinkedList

以雙向鏈表實現。鏈表無容量限制,但雙向鏈表自己使用了更多空間,也須要額外的鏈表指針操做。
按下標訪問元素–get(i)/set(i,e) 要悲劇的遍歷鏈表將指針移動到位(若是i>數組大小的一半,會從末尾移起)。
插入、刪除元素時修改先後節點的指針便可,但仍是要遍歷部分鏈表的指針才能移動到下標所指的位置,只有在鏈表兩頭的操做–add(), addFirst(),removeLast()或用iterator()上的remove()能省掉指針的移動。

CopyOnWriteArrayList

併發優化的ArrayList。用CopyOnWrite策略,在修改時先複製一個快照來修改,改完再讓內部指針指向新數組。
由於對快照的修改對讀操做來講不可見,因此只有寫鎖沒有讀鎖,加上覆制的昂貴成本,典型的適合讀多寫少的場景。若是更新頻率較高,或數組較大時,仍是Collections.synchronizedList(list),對全部操做用同一把鎖來保證線程安全更好。
增長了addIfAbsent(e)方法,會遍歷數組來檢查元素是否已存在,性能可想像的不會太好。

補充

不管哪一種實現,按值返回下標–contains(e), indexOf(e), remove(e) 都需遍歷全部元素進行比較,性能可想像的不會太好。
沒有按元素值排序的SortedList,在線程安全類中也沒有無鎖算法的ConcurrentLinkedList,湊合着用Set與Queue中的等價類時,會缺乏一些List特有的方法。
Map
HashMap

以Entry[]數組實現的哈希桶數組,用Key的哈希值取模桶數組的大小可獲得數組下標。

插入元素時,若是兩條Key落在同一個桶(好比哈希值1和17取模16後都屬於第一個哈希桶),Entry用一個next屬性實現多個Entry以單向鏈表存放,後入桶的Entry將next指向桶當前的Entry。

查找哈希值爲17的key時,先定位到第一個哈希桶,而後以鏈表遍歷桶裏全部元素,逐個比較其key值。

當Entry數量達到桶數量的75%時(不少文章說使用的桶數量達到了75%,但看代碼不是),會成倍擴容桶數組,並從新分配全部原來的Entry,因此這裏也最好有個預估值。

取模用位運算(hash & (arrayLength-1))會比較快,因此數組的大小永遠是2的N次方, 你隨便給一個初始值好比17會轉爲32。默認第一次放入元素時的初始值是16。

iterator()時順着哈希桶數組來遍歷,看起來是個亂序。

在JDK8裏,新增默認爲8的閥值,當一個桶裏的Entry超過閥值,就不以單向鏈表而以紅黑樹來存放以加快Key的查找速度。

LinkedHashMap

擴展HashMap增長雙向鏈表的實現,號稱是最佔內存的數據結構。支持iterator()時按Entry的插入順序來排序(可是更新不算, 若是設置accessOrder屬性爲true,則全部讀寫訪問都算)。

實現上是在Entry上再增長屬性before/after指針,插入時把本身加到Header Entry的前面去。若是全部讀寫訪問都要排序,還要把先後Entry的before/after拼接起來以在鏈表中刪除掉本身。

TreeMap

以紅黑樹實現,篇幅所限詳見入門教程。支持iterator()時按Key值排序,可按實現了Comparable接口的Key的升序排序,或由傳入的Comparator控制。可想象的,在樹上插入/刪除元素的代價必定比HashMap的大。

支持SortedMap接口,如firstKey(),lastKey()取得最大最小的key,或sub(fromKey, toKey), tailMap(fromKey)剪取Map的某一段。

ConcurrentHashMap

併發優化的HashMap,默認16把寫鎖(能夠設置更多),有效分散了阻塞的機率,並且沒有讀鎖。

數據結構爲Segment[],Segment裏面纔是哈希桶數組,每一個Segment一把鎖。Key先算出它在哪一個Segment裏,再算出它在哪一個哈希桶裏。

支持ConcurrentMap接口,如putIfAbsent(key,value)與相反的replace(key,value)與以及實現CAS的replace(key, oldValue, newValue)。

沒有讀鎖是由於put/remove動做是個原子動做(好比put是一個對數組元素/Entry 指針的賦值操做),讀操做不會看到一個更新動做的中間狀態。

ConcurrentSkipListMap

JDK6新增的併發優化的SortedMap,以SkipList實現。SkipList是紅黑樹的一種簡化替代方案,是個流行的有序集合算法,篇幅所限見入門教程。Concurrent包選用它是由於它支持基於CAS的無鎖算法,而紅黑樹則沒有好的無鎖算法。
很特殊的,它的size()不能隨便調,會遍從來統計。

補充

關於null,HashMap和LinkedHashMap是隨意的,TreeMap沒有設置Comparator時key不能爲null;ConcurrentHashMap在JDK7裏value不能爲null(這是爲何呢?),JDK8裏key與value都不能爲null;ConcurrentSkipListMap是全部JDK裏key與value都不能爲null。
Set
Set幾乎都是內部用一個Map來實現, 由於Map裏的KeySet就是一個Set,而value是假值,所有使用同一個Object。Set的特徵也繼承了那些內部Map實現的特徵。php

  • HashSet:內部是HashMap。java

  • LinkedHashSet:內部是LinkedHashMap。程序員

  • TreeSet:內部是TreeMap的SortedSet。算法

  • ConcurrentSkipListSet:內部是ConcurrentSkipListMap的併發優化的SortedSet。數組

  • CopyOnWriteArraySet:內部是CopyOnWriteArrayList的併發優化的Set,利用其addIfAbsent()方法實現元素去重,如前所述該方法的性能很通常。安全


補充:好像少了個ConcurrentHashSet,原本也該有一個內部用ConcurrentHashMap的簡單實現,但JDK恰恰沒提供。Jetty就本身封了一個,Guava則直接用java.util.Collections.newSetFromMap(new ConcurrentHashMap()) 實現。
Queue
Queue是在兩端出入的List,因此也能夠用數組或鏈表來實現。

–普通隊列–

LinkedList

是的,以雙向鏈表實現的LinkedList既是List,也是Queue。它是惟一一個容許放入null的Queue。

ArrayDeque

以循環數組實現的雙向Queue。大小是2的倍數,默認是16。

普通數組只能快速在末尾添加元素,爲了支持FIFO,從數組頭快速取出元素,就須要使用循環數組:有隊頭隊尾兩個下標:彈出元素時,隊頭下標遞增;加入元素時,若是已到數組空間的末尾,則將元素循環賦值到數組[0](若是此時隊頭下標大於0,說明隊頭彈出過元素,有空位),同時隊尾下標指向0,再插入下一個元素則賦值到數組[1],隊尾下標指向1。若是隊尾的下標追上隊頭,說明數組全部空間已用完,進行雙倍的數組擴容。

PriorityQueue

用二叉堆實現的優先級隊列,詳見入門教程,再也不是FIFO而是按元素實現的Comparable接口或傳入Comparator的比較結果來出隊,數值越小,優先級越高,越先出隊。可是注意其iterator()的返回不會排序。

–線程安全的隊列–

ConcurrentLinkedQueue/ConcurrentLinkedDeque

無界的併發優化的Queue,基於鏈表,實現了依賴於CAS的無鎖算法。

ConcurrentLinkedQueue的結構是單向鏈表和head/tail兩個指針,由於入隊時須要修改隊尾元素的next指針,以及修改tail指向新入隊的元素兩個CAS動做沒法原子,因此須要的特殊的算法,篇幅所限見入門教程。

PriorityBlockingQueue

無界的併發優化的PriorityQueue,也是基於二叉堆。使用一把公共的讀寫鎖。雖然實現了BlockingQueue接口,其實沒有任何阻塞隊列的特徵,空間不夠時會自動擴容。

DelayQueue

內部包含一個PriorityQueue,一樣是無界的。元素需實現Delayed接口,每次調用時需返回當前離觸發時間還有多久,小於0表示該觸發了。

pull()時會用peek()查看隊頭的元素,檢查是否到達觸發時間。ScheduledThreadPoolExecutor用了相似的結構。

–線程安全的阻塞隊列–

BlockingQueue的隊列長度受限,用以保證生產者與消費者的速度不會相差太遠,避免內存耗盡。隊列長度設定後不可改變。當入隊時隊列已滿,或出隊時隊列已空,不一樣函數的效果見下表:


ArrayBlockingQueue

定長的併發優化的BlockingQueue,基於循環數組實現。有一把公共的讀寫鎖與notFull、notEmpty兩個Condition管理隊列滿或空時的阻塞狀態。

LinkedBlockingQueue/LinkedBlockingDeque

可選定長的併發優化的BlockingQueue,基於鏈表實現,因此能夠把長度設爲Integer.MAX_VALUE。利用鏈表的特徵,分離了takeLock與putLock兩把鎖,繼續用notEmpty、notFull管理隊列滿或空時的阻塞狀態。微信

 關注微信公衆帳號:apkbus 回覆一下數字近期文章推薦。或者點擊連接關注:Android開發者數據結構

回覆「1」,獲取-Android酷炫實用的開源框架(UI框架)架構

回覆「2」,獲取-Google將再也不支持Android Eclipse Tools併發

回覆「3」,獲取-小處顯逼格!APP界面如何在細節上提高氣質?

回覆「4」,獲取-Android終於官方支持按百分比來設置空間的寬高了

回覆「5」,獲取-最全最強解析:支付寶錢包系統架構內部剖析(架構圖)

回覆「6」,獲取-【源碼】jiandan煎蛋—高仿也如此的專業

回覆「7」,獲取-【乾貨】源碼《在路上》and《高仿淘寶客戶端》

回覆「8」,獲取-2015中國程序員生存報告,你苦你先看

回覆「9」,獲取-如何給你的Android安裝文件(APK)瘦身

回覆「0」,獲取-Google技術開發指南,給大學生自學的建議

     更多源碼教程請點擊這裏:愛開發APP源碼論壇

相關文章
相關標籤/搜索