关于 put

源码

1、put

调用 hash ,先求 key 的 哈希值,再调用 putVal 。

  1. /**
  2. * Associates the specified value with the specified key in this map.
  3. * If the map previously contained a mapping for the key, the old
  4. * value is replaced.
  5. * 将指定的值与该地图中指定的键相关联.
  6. * 如果该 Map 已经包含此键的映射,那么旧值被新值替换.
  7. *
  8. * @param key key with which the specified value is to be associated
  9. * @param value value to be associated with the specified key
  10. * @return the previous value associated with <tt>key</tt>, or
  11. * <tt>null</tt> if there was no mapping for <tt>key</tt>.
  12. * (A <tt>null</tt> return can also indicate that the map
  13. * previously associated <tt>null</tt> with <tt>key</tt>.)
  14. */
  15. public V put(K key, V value) {
  16. return putVal(hash(key), key, value, false, true);
  17. }
  18. /**
  19. * Computes key.hashCode() and spreads (XORs) higher bits of hash
  20. * to lower. Because the table uses power-of-two masking, sets of
  21. * hashes that vary only in bits above the current mask will
  22. * always collide. (Among known examples are sets of Float keys
  23. * holding consecutive whole numbers in small tables.) So we
  24. * apply a transform that spreads the impact of higher bits
  25. * downward. There is a tradeoff between speed, utility, and
  26. * quality of bit-spreading. Because many common sets of hashes
  27. * are already reasonably distributed (so don't benefit from
  28. * spreading), and because we use trees to handle large sets of
  29. * collisions in bins, we just XOR some shifted bits in the
  30. * cheapest possible way to reduce systematic lossage, as well as
  31. * to incorporate impact of the highest bits that would otherwise
  32. * never be used in index calculations because of table bounds.
  33. */
  34. /**
  35. * 计算 key.hashCode() 并将 hash 的高位分散(XORs)到低位.
  36. * 由于该表使用了2次方掩码,仅在当前掩码以上的位数不同的哈希值集将总是发生碰撞。
  37. * (已知的例子包括在小表中持有连续整数的Float键的集合)
  38. * 因此,我们应用一个转换,将高位的影响向下分散。在速度、实用性和位传播的质量之间有一个权衡。
  39. * 因为许多常见的哈希集已经是合理分布的(所以不受益于传播),而且因为我们使用树来处理大集的碰撞,
  40. * 我们只是以最便宜的方式 XOR 一些移位的比特,以减少系统损失,以及纳入最高比特的影响,
  41. * 否则由于表的界限,永远不会被用于索引计算。
  42. */
  43. static final int hash(Object key) {
  44. int h;
  45. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  46. }

2、putVal

  1. /**
  2. * Implements Map.put and related methods.
  3. *
  4. * @param hash hash for key
  5. * @param key the key
  6. * @param value the value to put
  7. * @param onlyIfAbsent if true, don't change existing value
  8. * @param evict if false, the table is in creation mode.
  9. * @return previous value, or null if none
  10. */
  11. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  12. boolean evict) {
  13. Node<K,V>[] tab; Node<K,V> p; int n, i;
  14. if ((tab = table) == null || (n = tab.length) == 0)
  15. n = (tab = resize()).length;
  16. if ((p = tab[i = (n - 1) & hash]) == null)
  17. tab[i] = newNode(hash, key, value, null);
  18. else {
  19. Node<K,V> e; K k;
  20. if (p.hash == hash &&
  21. ((k = p.key) == key || (key != null && key.equals(k))))
  22. e = p;
  23. else if (p instanceof TreeNode)
  24. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  25. else {
  26. for (int binCount = 0; ; ++binCount) {
  27. if ((e = p.next) == null) {
  28. p.next = newNode(hash, key, value, null);
  29. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  30. treeifyBin(tab, hash);
  31. break;
  32. }
  33. if (e.hash == hash &&
  34. ((k = e.key) == key || (key != null && key.equals(k))))
  35. break;
  36. p = e;
  37. }
  38. }
  39. if (e != null) { // existing mapping for key
  40. V oldValue = e.value;
  41. if (!onlyIfAbsent || oldValue == null)
  42. e.value = value;
  43. afterNodeAccess(e);
  44. return oldValue;
  45. }
  46. }
  47. ++modCount;
  48. if (++size > threshold)
  49. resize();
  50. afterNodeInsertion(evict);
  51. return null;
  52. }

3、putVal 源码注释分析


/**
 * Implements Map.put and related methods.
 *
 * @param hash hash for key
 * @param key the key
 * @param value the value to put
 * @param onlyIfAbsent if true, don't change existing value
 * @param evict if false, the table is in creation mode.
 * @return previous value, or null if none
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {

    // 定义辅助变量,先讲一下几个变量分别存储的是什么。
    // tab:指向 table,操作 tab 的同时,table 也会跟着改变。
    // n:table 的 length
    // i:key 对应的索引值,计算方法:(n - 1) & hash
    Node<K,V>[] tab; Node<K,V> p; int n, i;

    // table 是否为空
    if ((tab = table) == null || (n = tab.length) == 0)
        // 调用 resize 对 table 进行扩容,更新 n 为 table resize 后的长度
        n = (tab = resize()).length;

    // 令 p 指向索引处(table[i]),判断 p 是否为 null
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 索引处(table[i])为空,调用 newNode 生成一个新结点,令索引处(table[i])指向生成的新结点,完成结点的新增。
        tab[i] = newNode(hash, key, value, null);
    else {
        // p 不等于 null 

        // 定义辅助变量
        // e:当前处理的结点,差不多类似于游标的作用
        // k:e.key ,当前游标的 key,
        Node<K,V> e; K k;

        // 判断是否产生了哈希冲突,如果是,继续判断 key 是不是重复了
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            // key 已经在 hashMap 中存在,令游标 e 指向 p .
            e = p;

        // 如果 p 指向的结点是一颗红黑树
        else if (p instanceof TreeNode)
            // 按照红黑树的规则添加该结点
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

        // p 结点指向的结点是链表头
        else {
            // 遍历链表,记录元素个数
            for (int binCount = 0; ; ++binCount) {
                // 令游标 e 依次向后滑动
                // 如果 e 指向的结点为 null ,则说明遍历到的链表尾部
                if ((e = p.next) == null) {
                    // 调用 newNode 插入到 p.next
                    p.next = newNode(hash, key, value, null);

                    // 如果链表元素个数累计总和大于 8 
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        // 调用 treeifyBin 则将链表转换为红黑树
                        treeifyBin(tab, hash);
                    break;
                }

                // 在链表中发现了一样的元素
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // 不做处理
                    break;

                // 配合 for 循环第一句使用,令游标 e 依次向后滑动
                p = e;
            }
        }

        // 如果 e 不等于 null 
        if (e != null) { // existing mapping for key
            // 在 hashMap 中遇到了重复 key

            // 获取旧值
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                // 用新 value 覆盖旧 value
                e.value = value;

            // 扩展点 afterNodeAccess
            afterNodeAccess(e);
            return oldValue;
        }
    }

    // 修改次数加一
    ++modCount;
    // 如果当前 table size 大于扩容阈值,则调用 resize 对 table 扩容
    if (++size > threshold)
        resize();
    // 扩展点 afterNodeInsertion
    afterNodeInsertion(evict);
    // 添加成功,返回 null
    return null;
}

问题

1、key 对应的索引,是如何被计算出来的?

hash:通过调用 HashMap 的 hash 函数,可以得到任意对象的哈希值。 为了方便你阅读,我把代码贴在下面:

/**
 * Computes key.hashCode() and spreads (XORs) higher bits of hash
 * to lower.  Because the table uses power-of-two masking, sets of
 * hashes that vary only in bits above the current mask will
 * always collide. (Among known examples are sets of Float keys
 * holding consecutive whole numbers in small tables.)  So we
 * apply a transform that spreads the impact of higher bits
 * downward. There is a tradeoff between speed, utility, and
 * quality of bit-spreading. Because many common sets of hashes
 * are already reasonably distributed (so don't benefit from
 * spreading), and because we use trees to handle large sets of
 * collisions in bins, we just XOR some shifted bits in the
 * cheapest possible way to reduce systematic lossage, as well as
 * to incorporate impact of the highest bits that would otherwise
 * never be used in index calculations because of table bounds.
 */
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

n:table 的 length
i:索引值

算法:i = (n - 1) & hash

2、什么时候会触发 resize 进行扩容

3、什么情况下,会触发树化,由链表转换为红黑树?

关键点在这几行代码

// 如果链表元素个数大等于 7
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    // 调用 treeifyBin 则将链表转换为红黑树
    treeifyBin(tab, hash);
static final int TREEIFY_THRESHOLD = 8;

TREEIFY_THRESHOLD 是 HashMap 中定义的一个常量,当链表上的元素个数大等于 7 个时,就会触发树化条件,调用 treeifyBin 函数。

但是,treeifyBin 函数会先对 table length 做检查,如果 table 为空,或 table length 小于 64 ,就会调用 resize ,所以实际上就不是树化,而是扩容了。

4、modCount 有什么用?

modCount 记录的是 HashMap 对象修改的次数,在 HashMap 的 put(), get(), remove(), Interator() 等方法中,都使用了该属性。由于 HashMap 不是线程安全的,所以在迭代的时候,会将 modCount 赋值到迭代器的 expectedModCount 属性中,然后进行迭代,如果在迭代的过程中 HashMap 被其他线程修改了,modCount 的数值就会发生变化,这个时候 expectedModCount 和 ModCount 不相等,迭代器就会抛出 ConcurrentModificationException() 异常。

5、为什么要执行一些看似无用的空函数?

afterNodeAccess 和 afterNodeInsertion 方法实际上是 HashMap 预留给子类( LinkedHashMap )的扩展点,实际上是空方法。

// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }