java.lang.Object
↳ java.util.AbstractMap
↳ java.util.HashMap
public class HashMap extends AbstractMap implements Map, Cloneable, Serializable { }
HashMap是基於哈希表實現的,每個元素是一個key-value對,實現了Serializable、Cloneable接口,容許使用null值和null鍵。不保證映射的順序,內部經過單鏈表解決衝突問題,容量超過(容量*加載因子)時,會自動增加。(除了不一樣步和容許使用null以外,HashMap類與Hashtable大體相同)
HashMap不是線程安全的,若是想獲取線程安全的HashMapjava
Map map = Collections.synchronizedMap(new HashMap());
HashMap由數組+鏈表組成的,主幹是一個Entry數組,每個entry包含一個(key-value)鍵值對,鏈表則是主要爲了解決哈希衝突而存在的,HashMap經過key的hashCode來計算hash值,當hashCode相同時,經過「拉鍊法」解決衝突,以下圖所示。web
若是定位到的數組位置不含鏈表(當前entry的next指向null),那麼對於查找,添加等操做很快,僅需一次尋址便可;若是定位到的數組包含鏈表,對於添加操做,其時間複雜度依然爲O(1),由於最新的Entry會插入鏈表頭部,只須要簡單改變引用鏈便可,而對於查找操做來說,此時就須要遍歷鏈表,而後經過key對象的equals方法逐一比對查找。因此性能考慮,HashMap中的鏈表出現越少,性能纔會越好。
//默認初始化化容量,即16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //最大容量,即2的30次方 static final int MAXIMUM_CAPACITY = 1 << 30; //默認加載因子,當容器使用率達到75%的時候就擴容 static final float DEFAULT_LOAD_FACTOR = 0.75f; //HashMap內部的存儲結構是一個數組,此處數組爲空,即沒有初始化以前的狀態 static final Entry<?,?>[] EMPTY_TABLE = {}; //空的存儲實體 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; //實際存儲的key-value鍵值對的個數 transient int size; //擴容的臨界點,若是當前容量達到該值,則須要擴容了. //若是當前數組容量爲0時(空數組),則該值做爲初始化內部數組的初始容量 int threshold; //由構造函數傳入的指定負載因子 final float loadFactor; //修改次數,用於快速失敗機制 transient int modCount; //默認的threshold值 static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
構造方法主要完成容量和加載因子的設置算法
/** * 經過初始容量和狀態因子構造HashMap * @param initialCapacity 容量 * @param loadFactor 加載因子 */ public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0)//參數有效性檢查 throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY)//參數有效性檢查 initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor))//參數有效性檢查 throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; threshold = initialCapacity; //init方法在HashMap中沒有實際實現,不過在其子類如 linkedHashMap中就會有對應實現 init(); }
/** * 經過擴容因子構造HashMap,容量去默認值,即16 */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }
/** * 加載因子取0.75,容量取16,構造HashMap */ public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); }
/** * 經過其餘Map來初始化HashMap,容量經過其餘Map的size來計算,加載因子取0.75 */ public HashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); //初始化HashMap底層的數組結構 inflateTable(threshold); //添加m中的元素 putAllForCreate(m); }
/** * 存入一個鍵值對,若是key重複,則更新value * @param key 鍵值名 * @param value 鍵值 * @return 若是存的是新key則返回null,若是覆蓋了舊鍵值對,則返回舊value */ public V put(K key, V value) { //若是數組爲空,則新建數組 if (table == EMPTY_TABLE) { //初始化HashMap底層的數組結構 inflateTable(threshold); } //若是key爲null,則把value放在table[0]中 if (key == null) return putForNullKey(value); //生成key所對應的hash值 int hash = hash(key); //根據hash值和數組的長度找到:該key所屬entry在table中的位置i int i = indexFor(hash, table.length); /** * 數組中每一項存的都是一個鏈表, * 先找到i位置,而後循環該位置上的每個entry, * 若是發現存在key與傳入key相等,則替換其value。而後結束方法。 * 若是沒有找到相同的key,則繼續執行下一條指令,將此鍵值對存入鏈表頭 */ for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } //map操做次數加一 modCount++; //查看是否須要擴容,並將該鍵值對存入指定下標的鏈表頭中 addEntry(hash, key, value, i); //若是是新存入的鍵值對,則返回null return null; }
/** * 將該鍵值對存入指定下標的鏈表頭中 * @param hash hash值 * @param key 鍵值名 * @param value 鍵值 * @param bucketIndex 索引 */ void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { //當size超過臨界閾值threshold,而且即將發生哈希衝突時進行擴容 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); }
/** * 將鍵值對與他的hash值做爲一個entry,插入table的指定下標中的鏈表頭中 * @param hash hash值 * @param key 鍵值名 * @param value 鍵值 * @param bucketIndex 索引 */ void createEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }
static int hash(int h) { //此功能確保hash碼不一樣,有限數量的碰撞 h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
/** * 對數組擴容,即建立一個新數組,並將舊數組裏的東西從新存入新數組 * @param newCapacity 新數組容量 */ void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length //若是當前數組容量已經達到最大值了,則將擴容的臨界值設置爲Integer.MAX_VALUE(Integer.MAX_VALUE是容量的臨界點) if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } //建立一個擴容後的新數組 Entry[] newTable = new Entry[newCapacity]; //將當前數組中的鍵值對存入新數組 transfer(newTable, initHashSeedAsNeeded(newCapacity)); //用新數組替換舊數組 table = newTable; //計算下一個擴容臨界點 threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }
/** * 將現有數組中的內容從新經過hash計算存入新數組 * @param newTable 新數組 * @param rehash */ void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; //遍歷現有數組中的每個單鏈表的頭entry for (Entry<K,V> e : table) { //查找鏈表裏的每個entry while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } //根據新的數組長度,從新計算此entry所在下標i int i = indexFor(e.hash, newCapacity); //將entry放入下標i處鏈表的頭部(將新數組此處的原有鏈表存入entry的next指針) e.next = newTable[i]; //將鏈表存回下標i newTable[i] = e; //查看下一個entry e = next; } } }
/** * 返回此hashmap中存儲的鍵值對個數 * @return 鍵值對個數 */ public int size() { return size; }
/** * 根據key找到對應value * @param key 鍵值名 * @return 鍵值value */ public V get(Object key) { //若是key爲null,則從table[0]中取value if (key == null) return getForNullKey(); //若是key不爲null,則先根據key,找到其entry Entry<K,V> entry = getEntry(key); //返回entry節點裏的value值 return null == entry ? null : entry.getValue(); }
/** * 返回一個set集合,裏面裝的都是hashmap的value。 * 由於map中的key不能重複,set集合中的值也不能重複,因此能夠裝入set。 * 在hashmap的父類AbstractMap中,定義了Set<K> keySet = null; * 若是keySet爲null,則返回內部類KeySet。 * @return 含有全部key的set集合 */ public Set<K> keySet() { Set<K> ks = keySet; return (ks != null ? ks : (keySet = new KeySet())); }
/** * 返回一個Collection集合,裏面裝的都是hashmap的value。 * 由於map中的value能夠重複,因此裝入Collection。 * 在hashmap的父類AbstractMap中,定義了Collection<V> values = null; * 若是values爲null,則返回內部類Values。 */ public Collection<V> values() { Collection<V> vs = values; return (vs != null ? vs : (values = new Values())); }
/** * 刪除hashmap中的全部元素 */ public void clear() { modCount++; //將table中的每個元素都設置成null Arrays.fill(table, null); size = 0; }
/** * 根據key刪除entry節點 * @param key 被刪除的entry的key值 * @return 被刪除的節點的value,刪除失敗則返回null */ public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); }
/** * 根據key刪除entry節點 * @param key 被刪除的entry的key值 * @return 被刪除的節點,刪除失敗則返回null */ final Entry<K,V> removeEntryForKey(Object key) { if (size == 0) { return null; } //計算key的hash值 int hash = (key == null) ? 0 : hash(key); //計算所屬下標 int i = indexFor(hash, table.length); //找到下標所存儲的單鏈表的頭節點 Entry<K,V> prev = table[i]; Entry<K,V> e = prev; //迭代單鏈表找到要刪除的節點 while (e != null) { Entry<K,V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
「快速失敗」也就是fail-fast,它是Java集合的一種錯誤檢測機制。當多個線程對集合進行結構上的改變的操做時,有可能會產生fail-fast機制。 注意 :記住是有可能,而不是必定。 例如:假設存在兩個線程(線程一、線程2),線程1經過Iterator在遍歷集合A中的元素,在某個時候線程2修改了集合A的結構(是結構上面的修改,而不是簡單的修改集合元素的內容),這個時候程序就會拋出ConcurrentModificationException 異常,從而產生fail-fast機制。數組
使用一個Node數組來存儲數據,但這個Node多是鏈表結構,也多是紅黑樹結構。若是插入的key的hashcode相同,那麼這些key也會被定位到Node數組的同一個桶位。若是同一個桶位裏的key不超過8個,使用鏈表結構存儲。若是超過了8個,那麼會調用treeifyBin函數,將鏈表轉換爲紅黑樹。那麼即便hashcode徹底相同,因爲紅黑樹的特色,查找某個特定元素,也只須要O(logn)的開銷。也就是說put/get的操做的時間複雜度最差只有O(log n)。
/** * @param key * @return */ static final int hash(Object key) { int h; // h = key.hashCode() 爲第一步 取hashCode值 // h ^ (h >>> 16) 爲第二步 高位參與運算 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }