java.util包中,Map旗下的映射類有這六個:HashMap、LinkedHashMap、IdentityHashMap、WeakHashMap、TreeMap、EnumMap,下面一一分析
1.HashMap
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
聲明,繼承了AbstractMap類,實現了Map接口
下面看一組相關聯的成員變量和數據類型:
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;
...
}
從這裏我們就可以得出HashMap的數據結構:
首先HashMap是基於數組的,即這個table數組,該數組中存儲的每一個元素實質上是一個鏈表,也稱爲一個桶,桶裏是一些前後鏈接的Node
table數組的元素是怎樣組織的?什麼樣的Node會被放到一個桶裏?我們接着看:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//重點看i = (n - 1) & hash,此即爲尋桶算法,hash爲key的哈希值,n爲table的長度即桶的個數,(n - 1) & hash即爲該元素要存入的桶的編號,可以看出具有相同hash值的元素會被存入同一個桶裏,但一個桶裏不止有一種hash
//若桶裏沒有元素,則直接放入該元素
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
//若桶中不存在hash和key都與該元素相等的元素,則把該元素掛在最後
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//若在桶中存在hash和key都與該元素相等的元素,則認爲key已存在
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//若key已存在,用新value代替舊value,返回舊value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//找到該元素所在的桶
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//在桶中尋找hash和key與該元素都相等的元素
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
從元素的存取方法中可以看出,table元素(桶)的組織方式是,先算出key的hash值,然後利用(n - 1) & hash這個公式計算出桶的序號,即爲該元素要放入的桶。可以看出相同hash值的元素一定會放入同一個桶中,但一個桶中也會包含不同hash值的元素
元素在放入桶之前會被包裝成Node類型,在桶中以鏈表的形式存在。並且會先遍歷鏈表,如果已經有與該元素hash值和key值都相等的結點,則直接修改該結點的值,並返回原值;若沒有與該元素hash值和key值都相等的結點,則把該Node掛在鏈表最後
接下來看其他方法:
//構造方法
public HashMap(int initialCapacity, float loadFactor);
public HashMap(int initialCapacity);
public HashMap();
public HashMap(Map<? extends K, ? extends V> m);
initialCapacity(初始容量)、loadFactor(裝填因子)都有默認值,也可以人爲指定
初始容量指table數組的容量,即桶的個數
填充比達到裝填因子後會進行擴容,即容量變爲原來的兩倍,每個桶中的鏈表會按奇偶次序分到兩個桶中,減少了鏈表長度,提高了查找效率
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
這兩個閾值的含義分別是:如果鏈表長度達到8,就把該鏈表轉換爲紅黑樹;如果紅黑樹結點降低爲6個,就把紅黑樹轉換爲鏈表
HashMap的其他方法:
public int size();
public boolean isEmpty();
public V get(Object key);
public boolean containsKey(Object key);
public V put(K key, V value);
public void putAll(Map<? extends K, ? extends V> m);
public V remove(Object key);
public void clear();
public boolean containsValue(Object value);
//三種視圖,無序
public Set<K> keySet();
public Collection<V> values();
public Set<Map.Entry<K,V>> entrySet();
public V getOrDefault(Object key, V defaultValue);
public V putIfAbsent(K key, V value);
//key和value都相等時才刪除
public boolean remove(Object key, Object value);
//key和value都相等時才替換
public boolean replace(K key, V oldValue, V newValue);
public V replace(K key, V value);
//函數式接口編程
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction);
//遍歷無序
public void forEach(BiConsumer<? super K, ? super V> action);
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function);
最後需要注意一點,雖然HashMap的遍歷是無序的,但在jdk8中由於hash算法的改變,若key是由Integer或數字型的String組成,則遍歷次序呈現出一種按key升序排列的形式,但仍然要認爲HashMap的遍歷是無序的
2.LinkedHashMap
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
聲明,繼承了HashMap類,實現了Map接口
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);
}
}
可以看到其數據結構Entry繼承了HashMap的Node,並且新增了before、after兩個指針,正是這兩個指針保存了元素的插入次序
構造方法:
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
可以看到,如不明確指定,accessOrder參數默認爲false
accessOrder參數爲false時,視圖和遍歷方法按元素插入順序排列
accessOrder參數爲true時,視圖和遍歷方法按元素訪問次序排列(在插入順序的基礎上,每訪問過一個元素就將它排到最後)
所以LinkedHashMap的三個視圖
public Set<K> keySet();
public Collection<V> values();
public Set<Map.Entry<K,V>> entrySet();
以及其forEach方法:
public void forEach(BiConsumer<? super K, ? super V> action);
其順序都與accessOrder參數有關
其他方法與HashMap類似,不再贅述
3.IdentityHashMap
public class IdentityHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, java.io.Serializable, Cloneable
聲明,繼承了AbstractMap類,實現了Map接口,其內部結構與HashMap類似
看構造方法:
public IdentityHashMap();
public IdentityHashMap(int expectedMaxSize);
public IdentityHashMap(Map<? extends K, ? extends V> m);
其與HashMap的不同點爲,HashMap不允許出現key相同的情況,但IdentityHashMap卻有可能出現相同的key,我們可以從put方法中看出原因:
public V put(K key, V value) {
final Object k = maskNull(key);
retryAfterResize: for (;;) {
final Object[] tab = table;
final int len = tab.length;
int i = hash(k, len);
for (Object item; (item = tab[i]) != null;
i = nextKeyIndex(i, len)) {
//==運算符比較的是地址,而不是key的實際值
if (item == k) {
@SuppressWarnings("unchecked")
V oldValue = (V) tab[i + 1];
tab[i + 1] = value;
return oldValue;
}
}
final int s = size + 1;
// Use optimized form of 3 * s.
// Next capacity is len, 2 * current capacity.
if (s + (s << 1) > len && resize(len))
continue retryAfterResize;
modCount++;
tab[i] = k;
tab[i + 1] = value;
size = s;
return null;
}
}
對比一下HashMap和IdentityHashMap的put方法中判斷key相等的語句:
//HashMap
(k = p.key) == key || (key != null && key.equals(k))
//IdentityHashMap
item == k
可見HashMap當key的equals方法或==運算有一個爲真時,就判定key相等;而IdentityHashMap只有==運算爲真時,才判定key相等,所以會出現key的值相等的情況
其他方法與HashMap類似,不再贅述
4.WeakHashMap
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>
聲明,繼承了AbstractMap類,實現了Map接口
看構造方法:
public WeakHashMap(int initialCapacity, float loadFactor);
public WeakHashMap(int initialCapacity);
public WeakHashMap();
public WeakHashMap(Map<? extends K, ? extends V> m);
注意這個數據結構:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
可以看出WeakHashMap中的鍵值對Entry繼承了WeakReference,即弱引用,所以可能隨時被GC回收,這就是它與HashMap的不同點。這種特性可用於需要緩存的場景
看這個成員變量:
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
被回收的引用保存在這個隊列中,以備更新映射
其他方法與HashMap類似,不再贅述
5.TreeMap
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable
聲明,繼承了AbstractMap類,實現了NavigableMap接口
TreeMap是有序的映射,用紅黑樹進行排序,且有一系列的導航方法
private final Comparator<? super K> comparator;
根據比較器來進行key的排序
看構造方法:
public TreeMap();//無構造器,按key的自然序升序排列
public TreeMap(Comparator<? super K> comparator);
public TreeMap(Map<? extends K, ? extends V> m);
public TreeMap(SortedMap<K, ? extends V> m);
看其他方法:
public int size();
public boolean containsKey(Object key);
public boolean containsValue(Object value);
public V get(Object key);
//返回當前的比較器
public Comparator<? super K> comparator();
public K firstKey();
public K lastKey();
public void putAll(Map<? extends K, ? extends V> map);
public V put(K key, V value);
public V remove(Object key);
public void clear();
public Map.Entry<K,V> firstEntry();
public Map.Entry<K,V> lastEntry();
public Map.Entry<K,V> pollFirstEntry();
public Map.Entry<K,V> pollLastEntry();
public Map.Entry<K,V> lowerEntry(K key);
public K lowerKey(K key);
public Map.Entry<K,V> floorEntry(K key);
public K floorKey(K key);
public Map.Entry<K,V> ceilingEntry(K key);
public K ceilingKey(K key);
public Map.Entry<K,V> higherEntry(K key);
public K higherKey(K key);
public Set<K> keySet() {return navigableKeySet();}
public NavigableSet<K> navigableKeySet();
public NavigableSet<K> descendingKeySet();
public Collection<V> values();
public Set<Map.Entry<K,V>> entrySet();
public NavigableMap<K, V> descendingMap();
public NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive);
public NavigableMap<K,V> headMap(K toKey, boolean inclusive);
public NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);
public SortedMap<K,V> subMap(K fromKey, K toKey);
public SortedMap<K,V> headMap(K toKey);
public SortedMap<K,V> tailMap(K fromKey);
public boolean replace(K key, V oldValue, V newValue);
public V replace(K key, V value);
public void forEach(BiConsumer<? super K, ? super V> action);
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function);
6.EnumMap
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements java.io.Serializable, Cloneable
聲明,繼承了AbstractMap類
private final Class<K> keyType;
keyType指key所屬的枚舉類型
看put方法:
public V put(K key, V value) {
typeCheck(key);
int index = key.ordinal();
Object oldValue = vals[index];
vals[index] = maskNull(value);
if (oldValue == null)
size++;
return unmaskNull(oldValue);
}
可以看出value存在vals數組中,其順序與枚舉key的順序是一致的,所以用key.ordinal()得到key的索引,也就得到了對應的value的索引。故其查找速度要比HashMap快
看構造方法:
public EnumMap(Class<K> keyType);
public EnumMap(EnumMap<K, ? extends V> m);
public EnumMap(Map<K, ? extends V> m);
再看其他方法:
public int size();
public boolean containsValue(Object value);
public boolean containsKey(Object key);
public V get(Object key);
public V put(K key, V value);
public V remove(Object key);
public void putAll(Map<? extends K, ? extends V> m);
public void clear();
public Set<K> keySet();
public Collection<V> values();
public Set<Map.Entry<K,V>> entrySet();