HashMap的put之1.8源码分析【细心总结】

// 冲突是指要存储添加数据位置上已经有数据了
// hash是key的哈希值,key|value是要被put的键值对
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    
    // tab:存储数据的节点数组,p代表当前元素,n数组的长度,i代表计算的键值对的存储位置
    Node[] tab; Node p; int n, i;
    
    // 第一次添加元素进行扩容16
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    
    // 判断这个位置上i有没有数据:没有数据直接添加到这个位置上
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {// 这个位置上有数据
        
        // e是临时节点变量,k也是临时key,用于记录下一个元素.
        Node e; K k;
        // key哈希值和冲突key一样,且key值都相等==说明key已存在则将替换这个位置上的第一个元素
        if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
            e = p;// 这个位置上的第一个元素赋给e
        else if (p instanceof TreeNode)
            // 如果是红黑树,则按红黑树规则添加元素
            e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 如果是链表且链表第一个元素和我们要添加的元素key不同,依次判断当前元素的下一个节点是否有元素:
            for (int binCount = 0; ; ++binCount) {
                // 没有元素则直接添加到当前元素后面
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // 添加完判断是否超过8个元素
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);// 链表超过8个元素则转成红黑树存储(可提高查询效率)
                    break;
                }
                // 要添加的元素和链表上的元素依次比较,key一旦存在则替换
                if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                
                // 记录下一个元素,和链表上的元素依次判断
                p = e;
            }
        }
        
        // 对要添加元素的位置上的元素进行替换
        if (e != null) { // existing mapping for key
            // 记录元素的旧值
            V oldValue = e.value;
            // onlyIfAbsent为false,
            if (!onlyIfAbsent || oldValue == null)
                // 替换冲突位置上元素的值value
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

细心总结:

put的时候会先判断数组是否为空,为空则表示是第一次put进行resize扩容为16;然后通过哈希算法计算键值对在数组中的存储位置,判断这个位置上是否有数据:

  • ①没有数据,则插入到这个位置上 --情况1

  • ②有数据,key和这个位置上所有数据的key依次比较哈希值,判断是否有相等的:

    • 都没有相等的,说明key不存在,如果是红黑树则添加到红黑树中;如果是链表则添加到末尾并判断节点数据个数如果大于8则转成红黑树存储 --情况2

    • 有相等的,调用key的equal比较:

      • 返回true,说明key已存在,替换掉value

      • 返回false,继续第②步

这里也说明了一个情况,因为equal和hashCode方法具有联动性,equal和hashCode方法要同时重写,同时重写的规则要满足equal为true,哈希值一定也要相同;哈希值相同,equal不一定为true。


通俗理解版总结:

put的时候会先判断数组是否为空,为空则表示是第一次put进行resize扩容为16;然后通过哈希算法计算键值对在数组中的存储位置,判断这个位置上是否有数据:

  • ①没有数据,则插入到这个位置上 --情况1

  • ②有数据,这个位置上如果用红黑树存储则按红黑树规则添加元素;如果用链表存储,key依次和这个链表上的元素进行比较:

    • 如果key存在,则替换value

    • 如果key不存在,则在链表末尾添加元素(尾插法),每添加完都要判断当前位置上的元素个数是否大于8?转为红黑树存储:不做操作。

比较都是先比较哈希值是否相同,相同才调equal比较key是否相同。

你可能感兴趣的:(Java,哈希算法,散列表,java)