HashMap源码解析

121 阅读4分钟

HashMap源码中的一些重要常量

  1. DEFAULT_INITIAL_CAPACITY: HashMap的默认容量16
  2. MAXIMUM_CAPACITY: HashMap的最大支持容量,2^30
  3. DEFAULT_LOAD_FAC TOR: HashMap的默认加载因子:0.75
  4. TREEIFY_THRESHOLD: Bucket中链表长度大于该默认值,转化为红黑树:8
  5. UNTREEIFY_THRESHOLD: Bucket中红黑树存储的Node小于该默认值,转化为链表
  6. MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64.(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。)
  7. table:存储元素的数组,总是2的n次幂entrySet:存储具体元素的集
  8. size: HashMap中存储的键值对的数量
  9. modCount: HashMap扩容和结构改变的次数。threshold:扩容的临界值,=容量*填充因子
  10. loadFactor:填充因子 jdk7

在实例化之后,创建了一个长度为16的一维数组Entry[] table

当有一个数据要添加时(key-value),调用key所在类的HashCode()方法计算出key的哈希值,经过某种算法后算出此哈希值所在数组中的位置。

如果没有数据,则添加成功

若有数据(意味着此位置有一个或多个数据,这些数据以链表形式存在),则依次比较链表中的key值与要添加的key值的哈希值是否一样。

若不一样,则添加成功

若一样,则比较两个的内容是否相同,若相同,则覆盖原有数据。若不相同,则添加成功

注意:在不断的添加过程中会涉及到扩容问题,大小为扩容为原来的两倍,并将原有数据复制到扩容后的数组中 jdk8

相对于jdk7在底层方面的不同

  1. new HashMap() 底层没有创建一个长度为16的数组
  2. jdk8底层数组是Node[],而非Entry[]
  3. 首次调用put()方法时,底层创建长度为16的数组
  4. jdk7底层结构只有数组+链表。jdk8中底层结构:数组+链表+红黑树。当数组的某一个索引位置上的元素以链表形式存在的数据个数大于8且当前数组的长度大于64。此时此索引位置上的所有数据改为使用红黑树存储
jdk8中HashMap源碼解析

当首次创建一个HashMap实例时:HashMap map = new HashMap(); 并不会创建数组

    public HashMap() {
        // 只会初始化一个负载因子,默认大小为: static final float DEFAULT_LOAD_FACTOR = 0.75f;
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

当调用put方法时会传入如下参数

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
​

其中hash(key) 便是根据传入的key值计算出哈希值,计算方法如下

   static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

当进入putVal() 方法时,首先会先声明四个变量:Node<K,V>[] tab; Node<K,V> p; int n, i;接着会进行判断是否需要初始化。代码如下

  if ((tab = table) == null || (n = tab.length) == 0){
          n = (tab = resize()).length;
  }

当条件中两个条件中的任何一个条件满足,就会进行初始化。调用resize()方法进行初始化

table意思:该表在首次使用时初始化,并根据需要调整大小。分配时,长度始终是 2 的幂。我们还在某些操作中允许长度为零,以允许当前不需要的引导机制。

初始化过程在resize()方法中,首先会执行如下过程

 newCap = DEFAULT_INITIAL_CAPACITY;  // static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);   // 0.75*16=12

然后会把newThr赋值给临界值threshold,接着创建一个长度为16的数组。并把创建好的数组赋值给table,这样在下次执行put方法时,判断不为空,就不会执行初始化部分。

     threshold = newThr;
     @SuppressWarnings({"rawtypes","unchecked"})
     Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
     table = newTab;

最后返回创建出来的数组

return newTab;

返回出去后,会把数组给tab,数组长度赋给n

 n = (tab = resize()).length;

然后计算根据哈希值计算数据要存放的位置,若为空,则执行newNode方法添加数据。

 if ((p = tab[i = (n - 1) & hash]) == null){
      tab[i] = newNode(hash, key, value, null);
 }

若计算出来的位置上有数据,则比较此位置上的元素的哈希值与要存入数据的哈希值是否相等。若相等,则进一步比较它们的数据是否相等,若相等,则进行覆盖操作

if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))){
         e = p;
 }
// 执行覆盖操作
 if (e != null) { // existing mapping for key
        V oldValue = e.value;
        if (!onlyIfAbsent || oldValue == null){
               e.value = value;
        }
         afterNodeAccess(e);
         return oldValue;    
  }
             

若判断为false,接下来会判断是否为红黑树结构,是的话则执行红黑树的添加操作

else if (p instanceof TreeNode){
     e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);   
}

若不是红黑树结构,则会进入如下代码执行

 for (int binCount = 0; ; ++binCount) {
     
     // 如果下一个数据为空,则执行添加操作
      if ((e = p.next) == null) {
          p.next = newNode(hash, key, value, null);
          
          /*
          判断是否达到转换为红黑树的条件 static final int TREEIFY_THRESHOLD = 8,。
          在 treeifyBin(tab, hash);中还判断了一下数组长度是否大于64.
          只有上述两个条件同时达成,才会转换为红黑树。
         
           if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize(); 此方法进行扩容
          */
         if (binCount >= TREEIFY_THRESHOLD - 1) {// -1 for 1st
                treeifyBin(tab, hash);
         }
          break; //跳出循环
      }
     
     // 如果它们哈希值和内容都相等,跳出循环
      if (e.hash == hash &&
          ((k = e.key) == key || (key != null && key.equals(k)))){
                 break; //跳出循环
      }
       p = e;  // 把当前链表的指针赋值给p
}
​
// 若相同数据,则进行替换
if (e != null) { // existing mapping for key
     V oldValue = e.value;
     if (!onlyIfAbsent || oldValue == null)
          e.value = value;
      afterNodeAccess(e);
      return oldValue;
}

学识浅薄,如有错误,还望大佬指出!