Java – HashMap底层源码分析

hash构造函数分析

// 定义扩容因子为 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;

public HashMap() {
  // 在无参构造函数中没什么,只是给了一个扩容因子的值
  this.loadFactor = DEFAULT_LOAD_FACTOR; 
}

在无构造函数中,创建对象时,HashMap并未进行创建 table 数组。

 

添加成员分析

public V put(K key, V value) {
  // 当执行 put 方法时,会传入5个参数
  return putVal(hash(key), key, value, false, true);
 }

前4个参数分别是

 

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)

int hash:  利用 key值进行 hash 计算得出哈希特证码

K key: 成员的 key 值

V value:  成员的 value 值

boolean onlyIfAbsent :  当出现相同成员时是否保留(默认为 false)

 

putVal 方法分析

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        // 定义一个临时节点数组对象,用来存放 table[] 
        Node<K,V>[] tab; 
        // 定义一个临时节点对象
        Node<K,V> p; 
        // 用于记录成员数
        int n;  
        // 用于记录table数组索引值
        int i;

        // 先把 table[] 给tab,如果为空,或tab的长度为0,说明table第一次增加成员
        // 需要先创建一个table数组 resize()
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

        // 通过计算数组长度-1 再对将添加的成员 hash进行计算
        // 得出成员将被存储到 table 数组中的那个索引位置上
        // 并且判断,table 数组的这个索引上是否已有成员
        if ((p = tab[i = (n - 1) & hash]) == null)

            // 如果没有,直接新建一个节点并存到数组对应索引的位置上
            tab[i] = newNode(hash, key, value, null);
        else {

            // 定义一个临时节点 e ,以备后用
            Node<K,V> e; 
            K k;
            // 如果索引上有成员,那么 p 肯定有值,
            // 值就是当前存在数组中的成员,就拿这个成员的 hash 和将添加的成员
            // 进行 hash 对比,看看是否是同一个 key,
            // 如果 hash 一样,继续使用 equals 方法去对比成员的内容属性
            // 再一次判断是否完全一样(避免哈希碰撞)
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                // 如果完全一样,会把成员给到临时 e ,此时 e 是旧成员,留作后用(1情况)
                e = p;
            else if (p instanceof TreeNode)
                // 判断 p(旧成员) 是否为红黑树成员,如果是,
                // 就把 新成员,添加到红黑树结构中,并给到 e (情况2)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                // 如果旧成员和新成员不相同的情况下

                // 取出最先的成员,并通过链表的方式,进行一个个的往下对比
                for (int binCount = 0; ; ++binCount) {
                    // 取出旧成员的下一个成员的地址,给到 e
                    if ((e = p.next) == null) {
                        // 如果 e 是空的,说明这个成员下面没而节点
                        // 于是把新成员数据创建一个成员,并挂在旧成员下面
                        p.next = newNode(hash, key, value, null);
                        // 判断链表是否超过8,超过8的话,就改为红黑树节点
                        if (binCount >= TREEIFY_THRESHOLD - 1) 
                            treeifyBin(tab, hash);
                        // 如果是通过增加节点,说明没有相同数据,所以直接跳出循环
                        break;
                    }

                    // 对比 e 的 hash 和现在的新成员的 hash 并对比是否完全相等
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                        // 如果是,直接跳出循环(情况3)
                        break;

                    // 如果链表还没有结束,就会把 e 给到p 让p 在下一个循环中
                    // 此时e的值是p.next,这里相当于把 p = p.next,给下一次循环对比节点用 
                    p = e;
                }
            }
            // 如果 e 不为空 说明(情况3)存在相同 key 值
            if (e != null) { 
                // 把 旧的成员的 value 值取出来
                V oldValue = e.value;
                // 判断 是否要保贸,默认不保留 !onlyIfAbsent 为 true
                if (!onlyIfAbsent || oldValue == null)
                    // 把 新成员的 value 值 覆盖 旧成员 的 value 值
                    e.value = value;
                afterNodeAccess(e);
                // 并返回 旧的成员 value 值,这就是为什么 put 新成员时,会返回旧成员值的原因
                return oldValue;
            }
        }
        ++modCount;
        // 扩容相关
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

 

 

 

 

 

如果您喜欢本站,点击这儿不花一分钱捐赠本站

这些信息可能会帮助到你: 下载帮助 | 报毒说明 | 进站必看

修改版本安卓软件,加群提示为修改者自留,非本站信息,注意鉴别

THE END
分享
二维码
打赏
海报
Java – HashMap底层源码分析
通过对实现源码进行分析,了解HashMap是如何存储数据的,它的数据结构如何。
<<上一篇
下一篇>>