HashMap 中 put() 方法详解

155 阅读4分钟

1.前言

本片文章主要讲一下hashMap 中的 put 方法,主要是用来向hashMap中放入元素的相关操作

2. 源码

public V put(K key, V value) {
    // 先获取当前key 的hash 然后调用的 putval方法,
    return putVal(hash(key), key, value, false, true);
}

// 获取hash值的方法
static final int hash(Object key) {
    int h;
    // 按位异或运算符(^)是二元运算符,要化为二进制才能进行计算,
    // 在两个操作数中,如果两个相应的位相同,则运算结果为0,否则1
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

2.1 putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) 详解

  1. resize() 详解
  2. putTreeVal() 详解
  3. threshold如何确定
/**
 * @param hash key的hash值
 * @param key  存储的key
 * @param value 存储的value
 * @param onlyIfAbsent 当这个为true的时候,若当前key在hashmap 中有值(不为null),则不会修改旧值
 * @param evict 如果它是false 表示hashmap的表处于创建模式
 * @return previous value, or null if none
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    /**
    tab : 创建临时节点表
    p : 临时节点
    n : hashMap.table 的长度
    i : 记录table的索引
    */
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 将hashMap的table 赋给临时变量 table , 当 tab == null 时 调用 resize()方法进行初始化
    // 当 tab !=  null 接着判断 tab的长度也不能为0 , 否则还是调用 resize() 方法进行初始化。
    // 这里 resize() 方法我在前面讲过,链接贴在上面了
    if ((tab = table) == null || (n = tab.length) == 0)
        // 把初始化后的 table 赋给 tab , table的长度赋给 n
        n = (tab = resize()).length;
    // (n - 1) & hash 这里确定当前元素插入的位置,这里为什么是(n-1)&hash 也在 resize()方法中讲过
    // 当这个地方的元素为null时,则说明以前没有放元素进来,这里可以直接插入
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 在索引为i的位置,创建一个新节点直接放在这里即可
        tab[i] = newNode(hash, key, value, null);
    else {  // 这里是处理当前位置已经有元素的情况下,可能是以链表的形式放入,也可能是以红黑树的形式放入
        Node<K,V> e; K k;
        // 这里即 p已经保存了 tab[i = (n - 1) & hash]上的元素,且不为null
        // p的hash 和 插入的hash相等 且 p的key等于 插入的key(这里等于表示 == 或者重写equals返回true)
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            // 定义的节点e 保存 节点 p
            e = p;
        // 判断节点p 是否是一个红黑树
        else if (p instanceof TreeNode)
            // 如果是一个红黑树,则调用红黑树的 putTreeVal 方法,详解见上方链接
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 否则的话,说明它是一个链表结构,则遍历链表,这里bitCount 记录的是链表长度,
            // 当链表长度 >= 7 会将该链表进行树化
            for (int binCount = 0; ; ++binCount) {
                // 如果 p.next == null ,则直接新建一个节点并让p.next 指向它
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // 如果链表长度 >= 7 ,调用 treeifyBin(tab, hash) 进行树化
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        // 树化 详解见 2.2 
                        treeifyBin(tab, hash);
                    // 树化后,跳出出循环
                    break;
                }
                // 这里判断,当e.hash和传入hash相同时,且他们的key也相同,则跳出循环
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 当e != null ,说明这个key对应的映射关系已经存在
        if (e != null) { // existing mapping for key
            // 取出原先的值
            V oldValue = e.value;
            //当onlyIfAbsent 为false 或  oldValue == null时,会对旧值进行覆盖
            if (!onlyIfAbsent || oldValue == null)
                // 把e的value设为新传入value
                e.value = value;
            // 这个方法是一个后置处理器,在HashMap中是空实现
            // 在LinkHashMap 中有默认实现 
            afterNodeAccess(e);
            // 返回旧值
            return oldValue;
        }
    }
    // modCount:记录hashMap修改的次数
    ++modCount;
    // size:hashMap的size ,当它 大于 threshold(达到它就会扩容,详解可见上方链接)时 会进行扩容操作
    if (++size > threshold)
        resize();
    // 也是一个类似于后置处理器的null 方法,在LinkHashMap 中有默认实现
    afterNodeInsertion(evict);
    return null;
}

2.2 treeifyBin() 方法详解

  1. resize()方法详解
  2. treeify()方法详解
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    // 这里会判断,当数组的长度 < 64时,即使链表长度超过7 也不会树化,会调用resize()方法进行扩容,详解见上方链接
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    // 这里取出当前元素要插入的数组中的索引位置记为 index,取出tab[index] 元素记为 e,判断当它不为null时进入  else if 
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        // hd : 链表头结点; tl:链表尾结点
        TreeNode<K,V> hd = null, tl = null;
        // 这里做一个循环,遍历链表e
        do {
            // 创建一个节点p ,详解见下面
            TreeNode<K,V> p = replacementTreeNode(e, null);
            // 如果尾结点是null,就把 p赋给头结点
            if (tl == null)
                hd = p;
            // 如果尾结点不是null,就把尾结点设为p的前置节点,尾结点的next指向p
            else {
                p.prev = tl;
                tl.next = p;
            }
            // 尾结点设为p
            tl = p;
        } while ((e = e.next) != null);
        // 这里判断 hd != null 时 调用treeify()方法进行树化,详解见上方链接
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}

// 通过节点p 新建一个节点,并使它的next 指向 next节点。
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
    return new TreeNode<>(p.hash, p.key, p.value, next);
}