集合:Map
Map是什麼
特殊的集合接口
Map的特點
-
Map中的元素被稱爲鍵值對(key-value),一個key對應一個value,例如電話簿中每一個名字對應一個電話號碼(key不可重複,value可重複)
- key:Set組成
- value:List組成
-
底層實現是散列表,即數組+鏈表(List+Set)+[樹]
Map的實現類和子接口
- HashMap:線程不安全,無序,key不可重複(不可重複的原理與HashSet相同)
- Hashtable:線程安全,類似StringBuffer、Vector
- SortedMap
- TreeMap:可排序(原理與TreeSet相同),對key進行排序
- LinkedHashMap
TreeMap排序時,若選擇傳入構造器Comparator,則構造器的泛型類型必須與key的泛型類型一致
Map的迭代
- Map不能使用迭代器(沒有實現Iterable接口)
- **!!!**遍歷key:Set keySet()
package demo;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Demo1<E> {
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
map.put("1","a");
map.put("2","b");
map.put("3","c");
map.put("4","d");
//返回key的set集合
Set<String> keys = map.keySet();
for(String set : keys) {
System.out.println(set+"="+map.get(set));
}
}
}
- 遍歷value:Collection values()
package demo;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Demo1<E> {
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
map.put("1","a");
map.put("2","b");
map.put("3","c");
map.put("4","d");
Collection<String> values = map.values();
for(String str : values) {
System.out.println(str);
}
}
}
無法通過value得到key,所以這種方式的遍歷只能獲得Map的value
- **!!!**遍歷key-value:Set< Entry<k,v> > entrySet()
package demo;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class Demo1<E> {
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
map.put("1","a");
map.put("2","b");
map.put("3","c");
map.put("4","d");
Set<Entry<String,String>> entrys = map.entrySet();
for(Entry<String,String> entry : entrys) {
System.out.println(entry);
}
}
}
Entry<k,v>是Map的一個內部類
Map的API
- put(key,value):添加新的鍵值對(hashCode()根據key計算位置)
- 可能會出現的兩種情況:
- key不同,hashCode()的計算結果相同:鏈表方式存儲,原來的元素向後推
- key值相同:新的鍵值對覆蓋原來的鍵值對
- 可能會出現的兩種情況:
注意1(重要):key和value可以爲null
注意2:HashSet:新元素不會替換舊元素
- get(key):通過key獲得對應的value
- remove(key):通過key刪除整個鍵值對,返回被刪除的value
- 如果查詢不到鍵值對,返回null
- contains…(…):
- containsKey(key)
- containsValue(value)
- isEmpty()
- putAll(Map map)
HashMap
HashMap的底層實現
數組+單向鏈表+紅黑樹
- 當鏈表中的數量超過8個時,鏈表轉換爲紅黑樹,所以如果哈希衝突(不同元素的hashCode()得到相同的結果)多的話,數組的元素將會是紅黑樹
HashMap的長度與擴容方式
- HashMap的初始長度:16
- HashMap的加載因子:0.75
- 加載因子:當Map中的鍵值對數量達到長度的0.75時,Map自動擴容(Hashtable相同)
- HashMap的擴容方式:old*2
- 例如,Map的初始長度爲16,當Map中的鍵值對個數達到12個時,Map的長度自動擴容爲32
選擇加載因子爲0.75的原因:
- HashMap擴容時,鍵值對的位置都要進行重新計算
- 新插入的鍵值對的位置也要進行計算,當剩餘空間太小時,計算所需時間會變長
綜合考慮以上兩點,當加載因子爲0.75時,效率比較高
Hashtable的初始長度:11
Hashtable的加載因子:0.75
Hashtable的擴容方式:old*2+1
Set的初始長度、加載因子、擴容方式都與Map相同
LinkedHashMap
LinkedHashMap是什麼
LinkedList與HashMap的結合,底層仍然是HashMap
LinkedHashMap的實現
雙向鏈表+散列表
LinkedHashMap的特點
- key不可重複
- 有序(有序指元素的順序與添加的先後順序一致,並非是排序)
- 節點多了before和after屬性(對應LinkedList的provious和next)
Map實現類的對比
HashMap | Hashtable | LinkedHashMap | TreeMap | |
---|---|---|---|---|
元素排序 | 無序 | 無序 | 加入順序 | 字典順序 |
元素 | key和value可以爲null | 不接受null | key和value可以爲null | 僅接受value爲null |
底層實現 | 數組+單向鏈表+紅黑樹 | 數組+鏈表 | 散列表+雙向鏈表 | 紅黑樹 |
線程安全 | 非synchronized | synchronized | 非synchronized | 非synchronized |
線程同步 | 不同步 | 同步 | 不同步 | 不同步 |
效率 | 高 | 低 | ||
默認長度 | 16 | 11 | ||
加載因子 | 0.75 | 0.75 | ||
擴容方式 | old*2 | old*2+1 |
擴展:ConcurrentHashMap
出現原因
HashMap線程不安全,Hashtable性能不好
底層實現
由16個segment組成,每個sgment都是線程安全的HashMap(鎖分段技術)
- 將數據分成一段一段(segment)地存儲,然後給每個數據段配一把鎖,當一個線程佔用鎖訪問其中某個數據段時,其它數據段的數據也能被其它線程訪問
上述實現是基於JDK1.7,在JDK1.8中ConcurrentHashMap完全是在HashMap的基礎上進行加鎖
特點
- 對segment的改寫操作,不影響其它segment的使用
- 理想情況下,最多支持6個線程併發修改segment,這16個線程分別訪問不同的segment
- segment加鎖時,所以讀線程是不會受到阻塞的
使用場景
高併發
方法的實現原理
- get():不加鎖,先定位到segment,然後再找到頭結點進行讀取操作。因爲value是volatile變量,所以可以保證在競爭條件時讀取到最新的值,如果讀到的value是null,則數據可能正在被修改,那麼就調用方法ReadValueUnderLock(),加鎖保證讀到的數據是正確的。
- Put():加鎖,一律添加到hash鏈的頭部
- Remove():加鎖,由於next是final類型不可改變,所以必須把刪除的節點之前的節點都複製一遍。