HashMap 的儲存結構是 數組+單鏈表 的結構,以下圖(盜的圖):java
看構造函數:數組
/** * Constructs a new empty {@code HashMap} instance. */ @SuppressWarnings("unchecked") public HashMap() { table = (HashMapEntry<K, V>[]) EMPTY_TABLE; threshold = -1; // Forces first put invocation to replace EMPTY_TABLE } /** * Constructs a new {@code HashMap} instance with the specified capacity. * * @param capacity * the initial capacity of this hash map. * @throws IllegalArgumentException * when the capacity is less than zero. */ public HashMap(int capacity) { if (capacity < 0) { throw new IllegalArgumentException("Capacity: " + capacity); } if (capacity == 0) { @SuppressWarnings("unchecked") HashMapEntry<K, V>[] tab = (HashMapEntry<K, V>[]) EMPTY_TABLE; table = tab; threshold = -1; // Forces first put() to replace EMPTY_TABLE return; } if (capacity < MINIMUM_CAPACITY) { capacity = MINIMUM_CAPACITY; } else if (capacity > MAXIMUM_CAPACITY) { capacity = MAXIMUM_CAPACITY; } else { capacity = Collections.roundUpToPowerOfTwo(capacity); } makeTable(capacity); } /** * Constructs a new {@code HashMap} instance with the specified capacity and * load factor. * * @param capacity * the initial capacity of this hash map. * @param loadFactor * the initial load factor. * @throws IllegalArgumentException * when the capacity is less than zero or the load factor is * less or equal to zero or NaN. */ public HashMap(int capacity, float loadFactor) { this(capacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new IllegalArgumentException("Load factor: " + loadFactor); } /* * Note that this implementation ignores loadFactor; it always uses * a load factor of 3/4. This simplifies the code and generally * improves performance. */ } /** * Constructs a new {@code HashMap} instance containing the mappings from * the specified map. * * @param map * the mappings to add. */ public HashMap(Map<? extends K, ? extends V> map) { this(capacityForInitSize(map.size())); constructorPutAll(map); }
多個重載構造函數,能夠根據需求指定 加載因子 和 初始容量。多線程
加載因子:儲存的數據個數 > 容量 * 加載因子 的時候,容量會翻倍。默認是 0.75。app
初始容量:會自動更正爲 2 的冪次方 (最接近初始容量,但大於初始容量) ---- capacity = Collections.roundUpToPowerOfTwo(capacity);less
這是由於,容量爲 2 的冪次方數的時候,計算 hash 值的時候,發生碰撞的概率會更小,有興趣能夠本身研究研究。ide
再看,遍歷 HashMap 的時候,用到的方法。函數
/** * Returns a set containing all of the mappings in this map. Each mapping is * an instance of {@link Map.Entry}. As the set is backed by this map, * changes in one will be reflected in the other. * * @return a set of the mappings. */ public Set<Entry<K, V>> entrySet() { Set<Entry<K, V>> es = entrySet; return (es != null) ? es : (entrySet = new EntrySet()); }
返回由 mapping 組成的 Set 集合,每一個 mapping 是一個 Map.Entry,Map.Entry 就是 key-value 組成的鍵值對。post
看 Map.Entry 是什麼:this
/** * {@code Map.Entry} is a key/value mapping contained in a {@code Map}. */ public static interface Entry<K,V> { /** * Compares the specified object to this {@code Map.Entry} and returns if they * are equal. To be equal, the object must be an instance of {@code Map.Entry} and have the * same key and value. * * @param object * the {@code Object} to compare with this {@code Object}. * @return {@code true} if the specified {@code Object} is equal to this * {@code Map.Entry}, {@code false} otherwise. * @see #hashCode() */ public boolean equals(Object object); /** * Returns the key. * * @return the key */ public K getKey(); /** * Returns the value. * * @return the value */ public V getValue(); /** * Returns an integer hash code for the receiver. {@code Object} which are * equal return the same value for this method. * * @return the receiver's hash code. * @see #equals(Object) */ public int hashCode(); /** * Sets the value of this entry to the specified value, replacing any * existing value. * * @param object * the new value to set. * @return object the replaced value of this entry. */ public V setValue(V object); };
就是一個接口,注意其實現類就行。spa
注意到 entrySet 方法中,entrySet 爲 null 時,entrySet 賦值爲 new EntrySet(),看看 EntrySet 類:
private final class EntrySet extends AbstractSet<Entry<K, V>> { public Iterator<Entry<K, V>> iterator() { return newEntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Entry)) return false; Entry<?, ?> e = (Entry<?, ?>) o; return containsMapping(e.getKey(), e.getValue()); } public boolean remove(Object o) { if (!(o instanceof Entry)) return false; Entry<?, ?> e = (Entry<?, ?>)o; return removeMapping(e.getKey(), e.getValue()); } public int size() { return size; } public boolean isEmpty() { return size == 0; } public void clear() { HashMap.this.clear(); } }
當咱們遍歷 HashMap,先拿到 entrySet ,在獲取其迭代器 iterator。
public Iterator<Entry<K, V>> iterator() { return newEntryIterator(); }
iterator() 方法調用了 newEntryIterator() 方法,看看:
Iterator<Entry<K, V>> newEntryIterator() { return new EntryIterator(); }
返回了一個 EntryIterator 對象。
private final class EntryIterator extends HashIterator implements Iterator<Entry<K, V>> { public Entry<K, V> next() { return nextEntry(); } }
咱們使用迭代器調用 next() 方法的時候,其實是調用了 EntryIterator 父類 HashIterator 的 nextEntry() 方法。
private abstract class HashIterator { int nextIndex; HashMapEntry<K, V> nextEntry = entryForNullKey; HashMapEntry<K, V> lastEntryReturned; int expectedModCount = modCount; HashIterator() { if (nextEntry == null) { HashMapEntry<K, V>[] tab = table; HashMapEntry<K, V> next = null; while (next == null && nextIndex < tab.length) { next = tab[nextIndex++]; } nextEntry = next; } } public boolean hasNext() { return nextEntry != null; } HashMapEntry<K, V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (nextEntry == null) throw new NoSuchElementException(); HashMapEntry<K, V> entryToReturn = nextEntry; HashMapEntry<K, V>[] tab = table; HashMapEntry<K, V> next = entryToReturn.next; while (next == null && nextIndex < tab.length) { next = tab[nextIndex++]; } nextEntry = next; return lastEntryReturned = entryToReturn; } public void remove() { if (lastEntryReturned == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); HashMap.this.remove(lastEntryReturned.key); lastEntryReturned = null; expectedModCount = modCount; } }
可能會拋出 ConcurrentModificationException 異常。迭代時,注意多線程問題。
HashIterator 中 nextEntry 就是下一個 Entry 的值。注意到,nextEntry() 方法中,過濾了 Entry 爲 null 的對象。
構造函數中,給 nextEntry 賦值爲第一個不爲 null 的 Entry。
再看操做 HashMap 集合的方法。
先是 put() 方法:
/** * Maps the specified key to the specified value. * * @param key * the key. * @param value * the value. * @return the value of any previous mapping with the specified key or * {@code null} if there was no such mapping. */ @Override public V put(K key, V value) { if (key == null) { return putValueForNullKey(value); } int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) { if (e.hash == hash && key.equals(e.key)) { preModify(e); V oldValue = e.value; e.value = value; return oldValue; } } // No entry for (non-null) key is present; create one modCount++; if (size++ > threshold) { tab = doubleCapacity(); index = hash & (tab.length - 1); } addNewEntry(key, value, hash, index); return null; }
若是存入 key 值不爲 null,根據 key 值計算出 key 對應的 hash 值,根據 hash 值,算出鍵值對在 table 中的位置。
transient HashMapEntry<K, V>[] table,table 是一個數組。table[index] 返回的是一個 HashMapEntry,HashMapEntry 是一個單鏈表結構,如今看上面的圖,就好理解多了。
HashMap 就是一個單鏈表數組,這種結構是解決 hash 值的碰撞問題。
假設,存入多個 key-value 鍵值對,key1 計算出來的 hash 值,與 Key2 計算出來的 hash 值相等(可是 equals 方法卻不相等),那麼他們都要儲存到同一個位置,爲了解決這個問題,就使用鏈表來儲存這些發生碰撞的數據。
拿到 hash 值對應位置的鏈表,查看鏈表有沒有頭,即 table[index] 是否有值。
若是 table[index] 有值,則遍歷 key 值是否存在,存在則替換。
若是 table[index] 沒有值,或者 key 值不存在,addNewEntry(key, value, hash, index);
/** * Creates a new entry for the given key, value, hash, and index and * inserts it into the hash table. This method is called by put * (and indirectly, putAll), and overridden by LinkedHashMap. The hash * must incorporate the secondary hash function. */ void addNewEntry(K key, V value, int hash, int index) { table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]); }
將最新的值放入 table[index] 位置,其他的以鏈表形式掛到後面。
看 HashMapEntry 類:
static class HashMapEntry<K, V> implements Entry<K, V> { final K key; V value; final int hash; HashMapEntry<K, V> next; HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) { this.key = key; this.value = value; this.hash = hash; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V value) { V oldValue = this.value; this.value = value; return oldValue; } @Override public final boolean equals(Object o) { if (!(o instanceof Entry)) { return false; } Entry<?, ?> e = (Entry<?, ?>) o; return Objects.equal(e.getKey(), key) && Objects.equal(e.getValue(), value); } @Override public final int hashCode() { return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); } @Override public final String toString() { return key + "=" + value; } }
最重要的就是裏面的 next 成員變量,看到這個就知道,是鏈表,實際上就是一個 Wrapper 類。
若是存入 key 值爲 null 的鍵值對,走 putValueForNullKey 方法(說明 HashMap 支持存放 null 值,null 鍵):
private V putValueForNullKey(V value) { HashMapEntry<K, V> entry = entryForNullKey; if (entry == null) { addNewEntryForNullKey(value); size++; modCount++; return null; } else { preModify(entry); V oldValue = entry.value; entry.value = value; return oldValue; } }
entryForNullKey 爲 null,表示尚未存放過 key 值爲 null 的 Entry。有則替換,沒有新建,很簡單。
最後要注意的是 addNewEntry 和 preModify 方法,該方法被 LinkedHashMap 重寫了。
看 get() 方法:
/** * Returns the value of the mapping with the specified key. * * @param key * the key. * @return the value of the mapping with the specified key, or {@code null} * if no mapping for the specified key is found. */ public V get(Object key) { if (key == null) { HashMapEntry<K, V> e = entryForNullKey; return e == null ? null : e.value; } int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)]; e != null; e = e.next) { K eKey = e.key; if (eKey == key || (e.hash == hash && key.equals(eKey))) { return e.value; } } return null; }
key值爲null,直接返回 entryForNullKey。
和 put 同樣,計算出儲存的位置,拿到鏈表遍歷,key 相等,則返回。
思考一個問題,HashSet 的內部實現是使用的 HashMap,HashSet add 數據的時候,是存放到 HashMap 的 Key 中,仍是 Value 中???
看 remove() 方法:
/** * Removes the mapping with the specified key from this map. * * @param key * the key of the mapping to remove. * @return the value of the removed mapping or {@code null} if no mapping * for the specified key was found. */ @Override public V remove(Object key) { if (key == null) { return removeNullKey(); } int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); for (HashMapEntry<K, V> e = tab[index], prev = null; e != null; prev = e, e = e.next) { if (e.hash == hash && key.equals(e.key)) { if (prev == null) { tab[index] = e.next; } else { prev.next = e.next; } modCount++; size--; postRemove(e); return e.value; } } return null; }
仍然是對鏈表的操做。
大體就這樣吧,其他的方法本身翻翻源碼。