map,鍵值對的集合,由於和pojo的結構和map類似,經常相互轉換,或者作為帶有特定標識的數據的集合存儲方式二使用。
還是先放結論:
類型 | 數據結構 | 特點描述 |
---|---|---|
HashMap | 散列表(拉鏈法) | 最常用,無序,線程不安全 |
Hashtable | 散列表(拉鏈法) | 無序,線程安全 |
LinkedHashMap | 雙向鏈表+散列表(拉鏈法) | 有序(插入順),線程不安全 |
WeakHashMap | 散列表(拉鏈法) | 無序,線程不安全,弱引用鍵,保存對象被回收時,自動刪除 |
TreeMap | 紅黑樹 | 有序(自動排序),線程不安全 |
基本概念,何為哈希?
先扔個概念摘自百度百科,哈希:
Hash,一般翻譯做“散列”,也有直接音譯為“哈希”的,就是把任意長度的輸入(又叫做預映射, pre-image),通過散列算法,變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,所以不可能從散列值來確定唯一的輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。
關於散列表:
散列表(Hash table,也叫哈希表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
拉鏈法:
拉鏈法解決沖突的做法是:將所有關鍵字為同義詞的結點鏈接在同一個單鏈表中。若選定的散列表長度為m,則可將散列表定義為一個由m個頭指針組成的指針數組T[0..m-1]。凡是散列地址為i的結點,均插入到以T[i]為頭指針的單鏈表中。T中各分量的初值均應為空指針。在拉鏈法中,裝填因子α可以大於1,但一般均取α≤1
嗯,看了覺得很迷糊對吧,那么來一步步的說。
- 我們的map,是鍵值對,key-value的形式,每次存取都需要判別一次key,但是要是每次比較,每次遍歷,那樣實在是太慢了
- 所以呢,就變了一個函數hash(key),取得的結果是一個int,用這個int作為數組的下標,數組內容存儲對應的value。這種使用哈希算法作為下標值,在一個數組上進行存儲對象的方法就是散列表
- 但是呢,有可能hash(key)的結果是一樣的,但是key卻不一樣。為了避免這種沖突問題,數組存儲的內容不是直接存儲值,而是進行了一定變變換,或者鏈表或者序列,總之使得key是同一哈希值數據,在數組的同一位置上存儲。這就是拉鏈法。
嗯,干巴巴的說果然還是很難理解,那么,現在有以下數據,要用散列表(拉鏈法)的方式進行存儲,結果會是這么樣的呢?
-
數據:
{"aa","1"},{"ab","2"},{"bA","3"},{"bB","4"},{"bC","5"}
順便,在jdk1.8.0_121中,對String對象的哈希算法如下:public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
-
則計算以上數據key值得哈希值分別為: 3104,3105,3103,3104,3105
於是使用數組存儲的話,大體是這么個印象數組[3103] = {"bA","3"} 數組[3104] = {"aa","1"},{"bB","4"} 數組[3105] = {"ab","2"},{"bC","5"}
-
由於存在哈希值相同的情況,所以需要使用拉鏈法進行。由於要在數組同一位置存多個數據,采用單鏈的方式的話,需要記錄鏈表下一個,next的值,所以需要包裝一下。結果的話,大體是這種效果:
數組(3103) -> {"bA","3"} 數組(3104) -> {"aa","1"} -> {"bB","4"} 數組(3105) -> {"ab","2"} -> {"bC","5"}
HashMap
經過以上說明,基本已經對hash有了概念的話,接下來就方便了。
還是上源碼(jdk1.8.0_121,java.util.HashMap):
transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key; // 鍵
V value; // 值
Node<K,V> next; // 單鏈搜索用對象引用
Node(int hash, K key, V value, Node<K,V> next) {......}
public final K getKey() {......}
public final V getValue() {......}
public final String toString() {......}
public final int hashCode() {......}
public final V setValue(V newValue) {......}
public final boolean equals(Object o) {......}
}
這是HashMap的關鍵,hashmap本質是散列表,是Node<K,V>[] table。由於需要保存key-value的鍵值對,還使用了單鏈的拉鏈法,所以封裝了一個內部類class來進行存儲,包含用於存儲鍵的key,存儲值的value ,哈希相同時,存儲下一個對象的引用用的next。
另外,HashMap的方法是不帶synchronized的,所以非線程安全。
示例:
HashMap<String,String> hashMap = new HashMap<String,String>();
hashMap.put("aa","1");
hashMap.put("ab","2");
hashMap.put("bA","3");
hashMap.put("bB","4");
hashMap.put("bC","5");
Iterator iter = hashMap.entrySet().iterator();
while (iter.hasNext()){
Map.Entry entry = (Map.Entry)iter.next();
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
}
// 結果
// key:aa,value:1
// key:bB,value:4
// key:ab,value:2
// key:bC,value:5
// key:bA,value:3
Hashtable
由於HashMap和Hashtable非常相似,所以僅僅說一說Hashtable的區別。
- 繼承對象不同:
HashMap 繼承於AbstractMap,實現了Map、Cloneable、java.io.Serializable接口。
Hashtable 繼承於Dictionary,實現了Map、Cloneable、java.io.Serializable接口。
因此,Dictionary是類似字典的枚舉類,相對於AbstractMap,對鍵值對的支持沒有那么全 - Hashtable的方法有synchronized,是線程安全的。
注,有synchronized安全,Hashtable因為效率低,並不是HashMap的線程安全替代品,推薦使用java.util.concurrent.ConcurrentHashMap對象或者Collections.synchronizedMap(Map<K,V> m)的方法。 - 對於null:
HashMap的key、value都可以為null。
Hashtable的key、value都不可以為null。
其他的還有api不同,遍歷種類不同,遍歷順序不同容量初值不同,hash算法不同等,姑且知道即可
LinkedHashMap
首先,LinkedHashMap繼承於HashMap,所以HashMap的相關功能都具備。然后,觀察以下源碼:
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
private static final long serialVersionUID = 3801124242820219131L;
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
一個頭,一個尾,擴展節點指前指后,對,雙向鏈表。
LinkedHashMap比HashMap多維護的這個雙向鏈表保存了map內容的插入順序,並重寫了相關的方法,使得對LinkedHashMap遍歷時的,可以根據插入順序,有序的遍歷。
示例代碼:
LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap<String,String>();
linkedHashMap.put("aa","1");
linkedHashMap.put("ab","2");
linkedHashMap.put("bA","3");
linkedHashMap.put("bB","4");
linkedHashMap.put("bC","5");
Iterator iter3 = linkedHashMap.entrySet().iterator();
while (iter3.hasNext()){
Map.Entry entry = (Map.Entry)iter3.next();
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
}
// 結果
// key:aa,value:1
// key:ab,value:2
// key:bA,value:3
// key:bB,value:4
// key:bC,value:5
WeakHashMap
直降上例子:
Map<String, String> weak = new WeakHashMap<String, String>();
weak.put(new String("1"), "1");
weak.put(new String("2"), "2");
weak.put(new String("3"), "3");
weak.put(new String("4"), "4");
weak.put(new String("5"), "5");
weak.put(new String("6"), "6");
System.out.println(weak.size()); // 結果:6
System.gc(); //手動觸發 Full GC
try {
Thread.sleep(50); //我的測試中發現必須sleep一下才能看到不一樣的結果
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(weak.size()); // 結果:0
WeakHashMap的key對象,不被其他地方引用時,會在gc執行后自動被刪除。這就是WeakHashMap最重要的特點:弱引用
總之,記住這一點即可,除非是用於做監控程序什么的,不然WeakHashMap很少被使用。
TreeMap
還是先上例子:
TreeMap<String,String> treeMap = new TreeMap<String,String>();
treeMap.put("bB","4");
treeMap.put("bC","5");
treeMap.put("aa","1");
treeMap.put("ab","2");
treeMap.put("bA","3");
Iterator iter4 = treeMap.entrySet().iterator();
while (iter4.hasNext()){
Map.Entry entry = (Map.Entry)iter4.next();
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
}
// 結果
// key:aa,value:1
// key:ab,value:2
// key:bA,value:3
// key:bB,value:4
// key:bC,value:5
可以發現,遍歷的輸出結果,自動的進行了排序(默認按照鍵,可在構造中設置Comparator)。
另外,TreeMap的本質是紅黑樹,適用於有序的整理遍歷。關鍵代碼如下:
private transient Entry<K,V> root;
static final class Entry<K,V> implements Map.Entry<K,V> {
K key; // 鍵
V value; // 值
Entry<K,V> left; // 左節點
Entry<K,V> right; // 右節點
Entry<K,V> parent; // 父節點
boolean color = BLACK;// 自身顏色
Entry(K key, V value, Entry<K,V> parent) {......}
public K getKey() {......}
public V getValue() {......}
public V setValue(V value) {......}
public boolean equals(Object o) {......}
public int hashCode() {......}
public String toString() {......}
}
紅黑樹比較麻煩,之后再介紹一些數據在說一說,這里對TreeMap,記住本質是紅黑樹,自動排序即可。
PS:set集合
map大體就是以上說明,趁着這個機會,也把set稍微總結下吧。
- 首先,set繼承於Collection,所以類似於List,使用add(),remove()的方法。
- 但是,由於set要求使用和map的key一樣的,對key進行判斷,不允許重復的特性,所以效果上和map的key非常相似。
- 因此,set的底層,甚至是直接使用了map對應的對象,直接利用map的key執行實現其不允許重復的特性
|類型|實現使用map對象|數據結構|特點描述|
|---|---|---------|
|HashSet|HashMap|散列表(拉鏈法)|最常用,無序,線程不安全|
|LinkedHashSet|LinkedHashMap|雙向鏈表+散列表(拉鏈法)|有序(插入順),線程不安全|
|TreeMap|TreeMap|紅黑樹|有序(自動排序),線程不安全|