一、HashMap實現原理

1. HashMap概述

  HashMap是基於雜湊表的Map介面的非同步實現。它允許存入null值和null鍵。它不保證存入元素的順序與操作順序一致,主要是不保證元素的順序永恆不變。

  HashMap底層的資料結構是一個“連結串列雜湊“的資料結構,即陣列和連結串列的結合體。

  從上圖中可以看出,HashMap底層就是一個數組,陣列的每一個位置上又是一個連結串列。

2.底層程式碼分析

  HashMap<String,Object> map = new HashMap<String,Object>();//當我們建立一個HashMap的時候,會產生哪些操作?

  

  1. public class HashMap<K,V>
  2. extends AbstractMap<K,V>
  3. implements Map<K,V>, Cloneable, Serializable
  4. {
  5.  
  6. /**
  7. * 初始化容量-16
  8. */
  9. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
  10.  
  11. static final int MAXIMUM_CAPACITY = 1 << 30;
  12.  
  13. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  14.  
  15. /**
  16. * 一個空的Entry陣列
  17. */
  18. static final Entry<?,?>[] EMPTY_TABLE = {};
  19.  
  20. /**
  21. * 儲存元素的陣列,自動擴容
  22. */
  23. transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
  24.   
  1.   /**
  2. * 鍵值對
  3. */
  4.   static class Entry<K,V> implements Map.Entry<K,V> {
  5. final K key;
  6. V value;
  7. Entry<K,V> next;
  8. int hash;
  9.  
  10. /**
  11. * 初始化方法
  12. */
  13. Entry(int h, K k, V v, Entry<K,V> n) {
  14. value = v;
  15. next = n;
  16. key = k;
  17. hash = h;
  18. }
  19.  
  20.   /** * 1.初始化方法 */
  21.   public HashMap() {
  22. this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
  23. }
  1.   /** * 2.初始化方法 */
  2.   public HashMap(int initialCapacity, float loadFactor) {
  3. if (initialCapacity < 0)
  4. throw new IllegalArgumentException("Illegal initial capacity: " +
  5. initialCapacity);
  6. if (initialCapacity > MAXIMUM_CAPACITY)
  7. initialCapacity = MAXIMUM_CAPACITY;
  8. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  9. throw new IllegalArgumentException("Illegal load factor: " +
  10. loadFactor);
  11.  
  12. this.loadFactor = loadFactor;
  13. threshold = initialCapacity;
  14. init();
  15. }
  16.  
  17. }
  1. public class LinkedHashMap<K,V>
  2. extends HashMap<K,V>
  3. implements Map<K,V>
  4. {
  5.  
  6. private static final long serialVersionUID = 3801124242820219131L;
  7.  
  8. /**
  9. * 雙重連結串列的一個第一個元素
  10. */
  11. private transient Entry<K,V> header;
  12.   /** * 3.初始化方法 */
  13. @Override
  14. void init() {
  15. header = new Entry<>(-1, null, null, null);
  16. header.before = header.after = header;
  17. }
  18.  
  19.   /**
  20. * LinkedHashMap 中的entry繼承了hashMap中的entry
  21. */
  22. private static class Entry<K,V> extends HashMap.Entry<K,V> {
  23. // These fields comprise the doubly linked list used for iteration.
  24. Entry<K,V> before, after;
  25.      /** * 4.初始化方法 */
  26. Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
  27. super(hash, key, value, next);
  28. }
  29.  
  30. }

通過 hashMap中的成員變數Entry<K,V>[] table,可以看出,Entry就是陣列中的元素,每個Entry就是一個key-value鍵值對,它持有一個只指向下一個元素的引用,這就構成了連結串列的資料結構。

關於陣列的初始化時機不是我們在new HashTable的時候,實在我們第一次執行put()操作的時候:

  1.   public V put(K key, V value) {
  2. if (table == EMPTY_TABLE) {
           /**如果這是一個空的table,就進行初始化*/
  3. inflateTable(threshold);
  4. }
  5. if (key == null)
  6. return putForNullKey(value);
         /**通過hash演算法獲取hash值*/
  7. int hash = hash(key);
          /**根據hash值獲取在陣列中的位置*/
  8. int i = indexFor(hash, table.length);
        /**獲取陣列在 i 位置上的連結串列,並進行迴圈*/
  9. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  10. Object k;
           /**如果hash值相等,並且value值相等,value值會進行覆蓋,返回之前的value*/
  11. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  12. V oldValue = e.value;
  13. e.value = value;
  14. e.recordAccess(this);
  15. return oldValue;
  16. }
  17. }
  18.     // 如果在i位置的entry為null,或者value的值不相同,執行addEctity()方法
  19. modCount++;
  20. addEntry(hash, key, value, i);
  21. return null;
  22. }
  23.  
  24.   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;
      }
  25.  
  26.   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++;
      }
  1.   Entry(int h, K k, V v, Entry<K,V> n) {
      value = v;
      next = n;
      key = k;
      hash = h;
      }

根據hash演算法得出元素在陣列中的存放位置,如果改位置上已經存在元素,那麼這個位置上的元素將以連結串列的形式存放,新加入的放在頭部,後加入的在尾部。

  1. final int hash(Object k) {
  2.   int h = hashSeed;
  3.   if (0 != h && k instanceof String) {
  4.   return sun.misc.Hashing.stringHash32((String) k);
  5.   }
  6.  
  7.   h ^= k.hashCode();
  8.   h ^= (h >>> 20) ^ (h >>> 12);
  9.   return h ^ (h >>> 7) ^ (h >>> 4);
  10.   }

根據key的hash值來決定元素在陣列中的位置,如何計算這個位置就是hash演算法。通過hash演算法儘量使得陣列的每個位置上都只有一個元素,當我們再次get()的時候,直接去陣列中取就可以,不用再遍歷連結串列。

hash(int h)根據key的hashcode重新計算一次雜湊,此演算法加入了高位計算,防止低位不變,高位變化時,造成hash的衝突。