看到這個有點懵逼,一時還真不知道怎麼解釋,能讓徹底沒有接觸過的人都能聽懂java
想到生活中一個有意思的場景,和咱們使用Map很是像,拿着新華詞典查字數組
咱們這裏以拼音方式查詢字時,通常步驟以下:安全
再轉換看一下Map的工做原理(主要是HashMap)數據結構
經過hash()計算key,得出一個hash值(同字轉拼音)多線程
經過hash值,獲取Node在數組中的索引 (同經過拼音獲取頁碼)併發
獲取Node,而後遍歷Node#next
,查到咱們須要的節點(同在頁碼中找到對應的字)性能
JDK中實現了一些可以覆蓋絕大部分場景的Map容器,羅列一些常見的以下優化
HashMap
: 主要是利用key的hash來定位對應的元素HashTable
EnumMap
: key爲枚舉的map,根據枚舉的ordinal
做爲定位對應的元素 (用的比較少,後面不歸入分析範疇)TreeMap
LinkedHashMap
ConcurrentHashMap
根據是否線程安全進行分類線程
線程安全 | 非線程安全 |
---|---|
HashTable , ConcurrentHashMap |
HashMap , TreeMap , LinkedHashMap |
根據Map是否有序進行分類設計
有序 | 無序 |
---|---|
TreeMap , LinkedHashMap |
HashMap , ConcurrentHashMap , HashTable |
根據key怎麼獲取對應的元素
hash定位 | 其餘定位 |
---|---|
HashTable , ConcurrentHashMap , HashMap , LinkedHashMap |
TreeMap |
TreeMap比較有意思,要求指定一個比較器,或者key可自比較,並且若是塞入兩個不一樣的kv對,可是key經過比較器發現相等時,會用後入的kv對中的value替換前面的那個,即定位是根據比較器來的
根據底層數據結構進行分類
數組+鏈表 | 樹 |
---|---|
HashTable , ConcurrentHashMap , HashMap , LinkedHashMap |
TreeMap |
>>> 如何使用
最最多見的使用方式,三把斧便可,以下
// 1. 建立一個Map對象 Map<String, String> map = new HashMap<>(); // 2. 塞入kv數據對 map.put("key", "value"); // 3. 取出key對應的數據 String value = map.get("key");
其次就是使用的注意事項
ConcurrentHashMap
,不要用HashTable
)TreeMap
, LinkedHashMap
,實際後者用得更多)此外分享一個實際項目中關於HashMap的一個優化點
通常來講,HashMap的get(key)
方法是O(1)時間開銷,可是因爲獲取對應value,會頻繁的計算hash值,且不可避免的會產生Hash碰撞,這些都是會有額外的開銷(cpu和時間開銷)
咱們的一個應用中,存在大量的配置開關(用與各類預案,各類場景的切換)存在一個大的HashMap中,致使每次提供服務時,都會去這個Map中屢次查詢Map中的配置值,咱們作的優化是將Map映射到一個配置類,以此減小頻繁的hash操做
遺憾的是最後性能提高並非特別明顯,也就1-2毫秒的樣子...(若是系統的rt要求特別嚴格的能夠考慮從這個方面出發)
>>> 如何實現的
簡單來說,從兩個點出發,一是數據結構,二是如何向其中添加和取數據
底層存儲結構以下圖:
數組+鏈表(or紅黑樹),數組的容量,必然爲2的n次方
讀寫數據:
equals()
判斷相等另一點就是擴容
capacity * loadFactor
(通常是容量*0.75),則數組會出現擴容,擴爲原來的兩倍二者都是線程安全的,但底層的實現原理確實徹底不一樣
HashTable
synchronized
關鍵字,實行加鎖同步ConcurrentHashMap
HashMap無序,但實際的業務場景中,須要有序的地方還很多,通常來將,常見的順序要求是根據前後塞入Map容器的順序來肯定,此時能夠考慮採用 LinkedHashMap
,確保先塞入Map的,在遍歷時,優先出來
若是有比較複雜的排序場景,則能夠採用TreeMap
,使用的時候須要額外注意一些使用事項
通常遍歷就是下面三中場景了
// 遍歷kv for(Map.Entry<String, String> entry: map.entrySet()) { // .... } // 遍歷key值 for(Object key : map.keySet()) { // xxx } // 遍歷value值 for(Object value: map.values()) { // xxxx }
根據不一樣的場景選擇遍歷方式
上面的遍歷過程當中,都是不容許對Map進行增刪操做的,不然會拋一個併發修改異常;若是在遍歷過程當中,須要根據對應的值,作一些處理,採用迭代器方式, 一個demo以下
Map<String, String> map = new HashMap<>(); // .... Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, String> entry = iterator.next(); iterator.remove(); iterator.next(); }
蛋疼的問題,真要本身來設計的話,最簡單的就是HashTable這種全加鎖的機制;可是這種實際是強制使多線程串行工做了,若是須要併發工做呢?
除了ConcurrentHashMap
的鎖分段機制,感受能夠參考CopyOnWriteArrayList
的實現方式,來一個CopyOnWriteHashMap
,對寫進行加鎖,讀無鎖,具體的實現,有待完善...