HashMap 详解四

简介:

新增原理

调用 put() 方法新增 key-value, 实际是调用 putVal() 方法完成. key 为空索引位置是 0, 索引位置相同则通过链表方式保存, 当链表长度超过 8 后转成红黑树保存; 当 key-value 数量超过阈值, 就要将数组 resize.


newNode()

key-value 实际是先构造成 Node 对象, 再进行保存, 如下:

// 如果没有下一个节点, next 设置为空
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
    return new Node<>(hash, key, value, next);
}

putVal()

/**
 * hash: key的哈希值
 * key
 * value
 * onlyIfAbsent: 如果为 true, 不会覆盖原来的值
 * evict: 只有 afterNodeInsertion(evict) 会用到这个参数, 但是这个方法什么都没做
 * 如果数组存在本身存在相同的 key, 结果会返回原来的值, 如果没有, 就返回空值
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    // tab 表示数组, p 表示节点, n 表示数组长度, i表示索引值
    Node<K,V>[] tab; Node<K,V> p; int n, i;

    // 对于数组为空, 首先是要初始化数组, 详情看 resize 方法
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;

    // 对应索引没有节点, 把新节点保存上去
    // p 被赋值索引位置对应节点, 也就是父节点
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);

    // 索引位置已经有节点, 那么要进行链表或者树保存了
    else {
        // e 表示临时节点, k 表示临时 key
        Node<K,V> e; K k;

        // 比较父节点与新节点的哈希值, 实际可以不比较, 毕竟索引值一样说明哈希值也一样
        // 当前节点 key 与新节点 key 比较, 相等就用 e 指向当前节点
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;

        // 父节点 key 和新节点 key 不等, 并且是树型结构, 那么遍历树找到对应位置
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

        // 父节点 key 和新节点 key 不等, 并且是链表结构, 那么遍历链表找到对应位置
        else {
            // binCount 表示链表长度, 开始遍历链表
            for (int binCount = 0; ; ++binCount) {

                // 当前节点的 next 为空, 说明这是最后一个节点
                if ((e = p.next) == null) {
                    // 新建节点加到链表最后
                    p.next = newNode(hash, key, value, null);
                    // 如果链表长度超过 8, 把链表转换成树结构
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }

                // 当前节点 key 和新节点 key 相等, 终止链表的遍历
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;

                // p 指向下一个节点
                p = e;
            }
        }

        // 经过上面遍历, 如果数组本身已经保存与新节点 key 相等的节点, e 就会指向这个节点
        // 否则 e 会指向树或者链表的最后, 也就是空
        // 对于非空的 e, 视情况是否替换新的值
        // afterNodeAccess 是空方法, 什么都不做
        // 因为节点数量不变, 不需要扩容数组, 所以直接返回就行
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }

    // 记录 HashMap 结构修改的次数, 没用过
    ++modCount;
    // 节点数量大于阈值, 数组将会被扩容
    if (++size > threshold)
        resize();
    // 空方法
    afterNodeInsertion(evict);
    return null;
}

这里用到了关于树的两个方法, 一个给树新增节点的 putTreeVal, 另一个是把链表转换成树结构的 treeifyBin, 这两个方法留到下一 part 讲.


相关文章
|
7月前
|
存储 安全 Java
HashMap的详细解读
HashMap的详细解读
62 0
|
2月前
|
存储 Serverless C++
c++实现HashMap
这篇文章提供了一个用C++实现的简单HashMap类的示例代码,包括构造函数、put、get、remove和size方法,以及私有的hash函数,用于计算键的哈希值。该HashMap使用链地址法解决哈希冲突,适用于学习和理解哈希表的基本概念。
24 0
|
6月前
|
存储 安全 Java
HashMap详解
HashMap详解
|
7月前
|
存储 算法 索引
|
7月前
|
Dart 算法 Java
HashMap的0.75可能只是一个经验值
HashMap的0.75可能只是一个经验值
|
存储 算法
详解HashMap
1.hash code hash code是使用hash函数运算得到的一个值,是对象的身份证号码,用于对象的辨重。在同一运行周期,对同一个对象多次调用hashcode(),只要是用于equals()的内容未变,那么每次得到的hash码也应该不变。,但不同运行周期间hash码可能会不同。
110 0
|
存储 算法 安全
【HashMap】
【HashMap】
130 0
|
存储 安全 算法
再聊 HashMap
HashMap特点: KV 结构,K、V 都允许 null 值; 线程不安全,运行速度快,存取速度快; 非线程安全的
再聊 HashMap
|
安全 算法 数据挖掘
厉害了!把 HashMap 剖析的只剩渣了!
很高兴遇见你~ HashMap是一个非常重要的集合,日常使用也非常的频繁,同时也是面试重点。本文并不打算讲解基础的使用api,而是深入HashM
厉害了!把 HashMap 剖析的只剩渣了!
HashMap 中的一个“坑”!(1)
HashMap 中的一个“坑”!(1)
178 0
HashMap 中的一个“坑”!(1)