一、HashMap實現原理
1. HashMap概述
HashMap是基於雜湊表的Map介面的非同步實現。它允許存入null值和null鍵。它不保證存入元素的順序與操作順序一致,主要是不保證元素的順序永恆不變。
HashMap底層的資料結構是一個“連結串列雜湊“的資料結構,即陣列和連結串列的結合體。
從上圖中可以看出,HashMap底層就是一個數組,陣列的每一個位置上又是一個連結串列。
2.底層程式碼分析
HashMap<String,Object> map = new HashMap<String,Object>();//當我們建立一個HashMap的時候,會產生哪些操作?
- public class HashMap<K,V>
- extends AbstractMap<K,V>
- implements Map<K,V>, Cloneable, Serializable
- {
- /**
- * 初始化容量-16
- */
- static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
- static final int MAXIMUM_CAPACITY = 1 << 30;
- static final float DEFAULT_LOAD_FACTOR = 0.75f;
- /**
- * 一個空的Entry陣列
- */
- static final Entry<?,?>[] EMPTY_TABLE = {};
- /**
- * 儲存元素的陣列,自動擴容
- */
- transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
- /**
- * 鍵值對
- */
- static class Entry<K,V> implements Map.Entry<K,V> {
- final K key;
- V value;
- Entry<K,V> next;
- int hash;
- /**
- * 初始化方法
- */
- Entry(int h, K k, V v, Entry<K,V> n) {
- value = v;
- next = n;
- key = k;
- hash = h;
- }
- /** * 1.初始化方法 */
- public HashMap() {
- this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
- }
- /** * 2.初始化方法 */
- 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();
- }
- }
- public class LinkedHashMap<K,V>
- extends HashMap<K,V>
- implements Map<K,V>
- {
- private static final long serialVersionUID = 3801124242820219131L;
- /**
- * 雙重連結串列的一個第一個元素
- */
- private transient Entry<K,V> header;
- /** * 3.初始化方法 */
- @Override
- void init() {
- header = new Entry<>(-1, null, null, null);
- header.before = header.after = header;
- }
- /**
- * LinkedHashMap 中的entry繼承了hashMap中的entry
- */
- private static class Entry<K,V> extends HashMap.Entry<K,V> {
- // These fields comprise the doubly linked list used for iteration.
- Entry<K,V> before, after;
- /** * 4.初始化方法 */
- Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
- super(hash, key, value, next);
- }
- }
通過 hashMap中的成員變數Entry<K,V>[] table,可以看出,Entry就是陣列中的元素,每個Entry就是一個key-value鍵值對,它持有一個只指向下一個元素的引用,這就構成了連結串列的資料結構。
關於陣列的初始化時機不是我們在new HashTable的時候,實在我們第一次執行put()操作的時候:
- public V put(K key, V value) {
- if (table == EMPTY_TABLE) {
/**如果這是一個空的table,就進行初始化*/- inflateTable(threshold);
- }
- if (key == null)
- return putForNullKey(value);
/**通過hash演算法獲取hash值*/- int hash = hash(key);
/**根據hash值獲取在陣列中的位置*/- int i = indexFor(hash, table.length);
/**獲取陣列在 i 位置上的連結串列,並進行迴圈*/- for (Entry<K,V> e = table[i]; e != null; e = e.next) {
- Object k;
/**如果hash值相等,並且value值相等,value值會進行覆蓋,返回之前的value*/- if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }
- // 如果在i位置的entry為null,或者value的值不相同,執行addEctity()方法
- modCount++;
- addEntry(hash, key, value, i);
- return null;
- }
- void addEntry(int hash, K key, V value, int bucketIndex) {
// 如果當前陣列已經飽和,並且當前位置的entry不是null,陣列進行擴容
if ((size >= threshold) && (null != table[bucketIndex])) {
// 擴容操作,原陣列需要重新計算在新陣列中的位置,並放進去,這裡會產生效能損耗
// 如果我們能已知元素個數,就可以在建立的時候進行生命即可。
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
// 建立新的Entry
createEntry(hash, key, value, bucketIndex);
}
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
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);
}
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++;
}
- Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
根據hash演算法得出元素在陣列中的存放位置,如果改位置上已經存在元素,那麼這個位置上的元素將以連結串列的形式存放,新加入的放在頭部,後加入的在尾部。
- final int hash(Object k) {
- int h = hashSeed;
- if (0 != h && k instanceof String) {
- return sun.misc.Hashing.stringHash32((String) k);
- }
- h ^= k.hashCode();
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
- }
根據key的hash值來決定元素在陣列中的位置,如何計算這個位置就是hash演算法。通過hash演算法儘量使得陣列的每個位置上都只有一個元素,當我們再次get()的時候,直接去陣列中取就可以,不用再遍歷連結串列。
hash(int h)根據key的hashcode重新計算一次雜湊,此演算法加入了高位計算,防止低位不變,高位變化時,造成hash的衝突。