集合框架

ArrayList

以數(shù)組實(shí)現(xiàn)。節(jié)約空間,但數(shù)組有容量限制。超出限制時(shí)會(huì)增加50%容量,用System.arraycopy()復(fù)制到新的數(shù)組。因此最好能給出數(shù)組大小的預(yù)估值。默認(rèn)第一次插入元素時(shí)創(chuàng)建大小為10的數(shù)組。

按數(shù)組下標(biāo)訪問(wèn)元素-get(i)、set(i,e) 的性能很高,這是數(shù)組的基本優(yōu)勢(shì)。

如果按下標(biāo)插入元素、刪除元素-add(i,e)、 remove(i)、remove(e),則要用System.arraycopy()來(lái)復(fù)制移動(dòng)部分受影響的元素,性能就變差了。

越是前面的元素,修改時(shí)要移動(dòng)的元素越多。直接在數(shù)組末尾加入元素-常用的add(e),刪除最后一個(gè)元素則無(wú)影響。


LinkedList

以雙向鏈表實(shí)現(xiàn)。鏈表無(wú)容量限制,但雙向鏈表本身使用了更多空間,每插入一個(gè)元素都要構(gòu)造一個(gè)額外的Node對(duì)象,也需要額外的鏈表指針操作。

按下標(biāo)訪問(wèn)元素-get(i)、set(i,e) 要悲劇的部分遍歷鏈表將指針移動(dòng)到位 (如果i>數(shù)組大小的一半,會(huì)從末尾移起)。

插入、刪除元素時(shí)修改前后節(jié)點(diǎn)的指針即可,不在需要復(fù)制移動(dòng)。但還是要部分遍歷鏈表的指針才能移動(dòng)到下標(biāo)所指的位置。

只有在鏈表兩頭的操作-add()、addFirst()、removeLast()或用iterator()上的remove()倒能省掉指針的移動(dòng)。

Apache Commons 有個(gè)TreeNodeList,里面是棵二叉樹(shù),可以快速移動(dòng)指針到位。


CopyOnWriteArrayList

并發(fā)優(yōu)化的ArrayList?;诓豢勺儗?duì)象策略,在修改時(shí)先復(fù)制出一個(gè)數(shù)組快照來(lái)修改,改好了,再讓內(nèi)部指針指向新數(shù)組。

因?yàn)閷?duì)快照的修改對(duì)讀操作來(lái)說(shuō)不可見(jiàn),所以讀讀之間不互斥,讀寫之間也不互斥,只有寫寫之間要加鎖互斥。但復(fù)制快照的成本昂貴,典型的適合讀多寫少的場(chǎng)景。

雖然增加了addIfAbsent(e)方法,會(huì)遍歷數(shù)組來(lái)檢查元素是否已存在,性能可想像的不會(huì)太好。


HashMap

以Entry[]數(shù)組實(shí)現(xiàn)的哈希桶數(shù)組,用Key的哈希值取模桶數(shù)組的大小可得到數(shù)組下標(biāo)。

插入元素時(shí),如果兩條Key落在同一個(gè)桶(比如哈希值1和17取模16后都屬于第一個(gè)哈希桶),我們稱之為哈希沖突。

JDK的做法是鏈表法,Entry用一個(gè)next屬性實(shí)現(xiàn)多個(gè)Entry以單向鏈表存放。查找哈希值為17的key時(shí),先定位到哈希桶,然后鏈表遍歷桶里所有元素,逐個(gè)比較其Hash值然后key值。

在JDK8里,新增默認(rèn)為8的閥值,當(dāng)一個(gè)桶里的Entry超過(guò)閥值,就不以單向鏈表而以紅黑樹(shù)來(lái)存放以加快Key的查找速度。

當(dāng)然,最好還是桶里只有一個(gè)元素,不用去比較。所以默認(rèn)當(dāng)Entry數(shù)量達(dá)到桶數(shù)量的75%時(shí),哈希沖突已比較嚴(yán)重,就會(huì)成倍擴(kuò)容桶數(shù)組,并重新分配所有原來(lái)的Entry。擴(kuò)容成本不低,所以也最好有個(gè)預(yù)估值。

取模用與操作(hash & (arrayLength-1))會(huì)比較快,所以數(shù)組的大小永遠(yuǎn)是2的N次方, 你隨便給一個(gè)初始值比如17會(huì)轉(zhuǎn)為32。默認(rèn)第一次放入元素時(shí)的初始值是16。

iterator()時(shí)順著哈希桶數(shù)組來(lái)遍歷,看起來(lái)是個(gè)亂序。

問(wèn)題集錦

  1. HashMap為什么線程不安全?
    沒(méi)有任何的線程同步,多線程環(huán)境下對(duì)共享變量的操作(數(shù)據(jù)競(jìng)爭(zhēng))肯定就不安全了!
  2. 為什么當(dāng)一個(gè)桶里的Entry超過(guò)閥值8,就以紅黑樹(shù)來(lái)存放?
    單向鏈表時(shí)間復(fù)雜度是O(n),紅黑樹(shù)是O(logn),至于為什么是8,不知道?。?/li>

LinkHashMap

擴(kuò)展HashMap,每個(gè)Entry增加雙向鏈表,號(hào)稱是最占內(nèi)存的數(shù)據(jù)結(jié)構(gòu)。

支持iterator()時(shí)按Entry的插入順序來(lái)排序(如果設(shè)置accessOrder屬性為true,則所有讀寫訪問(wèn)都排序)。

插入時(shí),Entry把自己加到Header Entry的前面去。如果所有讀寫訪問(wèn)都要排序,還要把前后Entry的before/after拼接起來(lái)以在鏈表中刪除掉自己,所以此時(shí)讀操作也是線程不安全的了。


TreeMap

以紅黑樹(shù)實(shí)現(xiàn),紅黑樹(shù)又叫自平衡二叉樹(shù)

對(duì)于任一節(jié)點(diǎn)而言,其到葉節(jié)點(diǎn)的每一條路徑都包含相同數(shù)目的黑結(jié)點(diǎn)。
上面的規(guī)定,使得樹(shù)的層數(shù)不會(huì)差的太遠(yuǎn),使得所有操作的復(fù)雜度不超過(guò) O(lgn),但也使得插入,修改時(shí)要復(fù)雜的左旋右旋來(lái)保持樹(shù)的平衡。

支持iterator()時(shí)按Key值排序,可按實(shí)現(xiàn)了Comparable接口的Key的升序排序,或由傳入的Comparator控制。可想象的,在樹(shù)上插入/刪除元素的代價(jià)一定比HashMap的大。

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


EnumMap

EnumMap的原理是,在構(gòu)造函數(shù)里要傳入枚舉類,那它就構(gòu)建一個(gè)與枚舉的所有值等大的數(shù)組,按Enum. ordinal()下標(biāo)來(lái)訪問(wèn)數(shù)組。性能與內(nèi)存占用俱佳。

美中不足的是,因?yàn)橐獙?shí)現(xiàn)Map接口,而 V get(Object key)中key是Object而不是泛型K,所以安全起見(jiàn),EnumMap每次訪問(wèn)都要先對(duì)Key進(jìn)行類型判斷,在JMC里錄得不低的采樣命中頻率。


ConcurrentHashMap

并發(fā)優(yōu)化的HashMap。

在JDK7里的經(jīng)典設(shè)計(jì),默認(rèn)16把寫鎖(可以設(shè)置更多),有效分散了阻塞的概率。數(shù)據(jù)結(jié)構(gòu)為Segment[],每個(gè)Segment一把鎖。Segment里面才是哈希桶數(shù)組。Key先算出它在哪個(gè)Segment里,再去算它在哪個(gè)哈希桶里。

也沒(méi)有讀鎖,因?yàn)閜ut/remove動(dòng)作是個(gè)原子動(dòng)作(比如put的整個(gè)過(guò)程是一個(gè)對(duì)數(shù)組元素/Entry 指針的賦值操作),讀操作不會(huì)看到一個(gè)更新動(dòng)作的中間狀態(tài)。

但在JDK8里,Segment[]的設(shè)計(jì)被拋棄了,改為精心設(shè)計(jì)的,只在需要鎖的時(shí)候加鎖。

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

提問(wèn)

  1. 所以,使用concurrentHashMap就一定線程安全?

Set

所有Set幾乎都是內(nèi)部用一個(gè)Map來(lái)實(shí)現(xiàn), 因?yàn)镸ap里的KeySet就是一個(gè)Set,而value是假值,全部使用同一個(gè)Object即可。

Set的特征也繼承了那些內(nèi)部的Map實(shí)現(xiàn)的特征。

HashSet:內(nèi)部是HashMap。

LinkedHashSet:內(nèi)部是LinkedHashMap。

TreeSet:內(nèi)部是TreeMap的SortedSet。

ConcurrentSkipListSet:內(nèi)部是ConcurrentSkipListMap的并發(fā)優(yōu)化的SortedSet。

CopyOnWriteArraySet:內(nèi)部是CopyOnWriteArrayList的并發(fā)優(yōu)化的Set,利用其addIfAbsent()方法實(shí)現(xiàn)元素去重,如前所述該方法的性能很一般。

好像少了個(gè)ConcurrentHashSet,本來(lái)也該有一個(gè)內(nèi)部用ConcurrentHashMap的簡(jiǎn)單實(shí)現(xiàn),但JDK偏偏沒(méi)提供。Jetty就自己簡(jiǎn)單封了一個(gè),Guava則直接用java.util.Collections.newSetFromMap(new ConcurrentHashMap()) 實(shí)現(xiàn)。

參考資料

  1. 江南白衣-java8下的集合小抄
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容