ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

HashMap重要方法逐行解读

2021-07-24 19:31:48  阅读:148  来源: 互联网

标签:hash tab 阈值 树化 解读 key null 逐行 HashMap


HashMap源码解读

put方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

putVal方法

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;//定义了一些辅组变量
    if ((tab = table) == null || (n = tab.length) == 0)//将table赋值给tab,n记录tab的长度
        n = (tab = resize()).length;//如果tab为null或者  tab的长度为0,则对tab初始化,resize代码解读在下面,n为数组的长度
    if ((p = tab[i = (n - 1) & hash]) == null)//将i赋值为index,p赋值为 tab[i]的值,如果为null,说明这个位置没放过东西---------------1
        tab[i] = newNode(hash, key, value, null);//直接将内容 new一个新的Node,然后放入到tab[i]中
    else {//此时表示  tab[i]中已经放了内容-------------------------------------------------------------------------------------1
        Node<K,V> e; K k;//定义辅助变量
        if (p.hash == hash && //当前p指向的节点的hash是否等于传过来的hash
            ((k = p.key) == key || (key != null && key.equals(k))))//判断key的地址是否一样  再去判断  key的内容是否相等  这个判断顺序也是有讲究的,只有hash值相同,才有可能key相同,   如果key==p.key说明是同一个对象,后面的不需要执行,否则再去equals
            e = p;//如果满足上面的条件,说明传过来的key已经存在与table数组中  将e指针指向p,也就是e指向了这个节点
        else if (p instanceof TreeNode)//如果是红黑树,则进行红黑树的插入
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {//不是红黑树,链表   进行插入 节点的插入
            for (int binCount = 0; ; ++binCount) {//此时p已经是第1个节点的----------------------------2
                if ((e = p.next) == null) {//将e指向p的后面的节点,如果后面是null了,说明到了尾部了
                    p.next = newNode(hash, key, value, null);//直接插入
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 这里的-1就是因为p一开始就是第1个节点了,所以对于一开始而言,链表长度为8的时候,不会进行树化,只有第9个进来的时候才会进行树化
                        treeifyBin(tab, hash);//将链表进行树化的操作  注意,这里进去不会直接就树化,要判断是否满足if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); 如果tab为null或者tab的长度小于64,则resize
                    break;//跳出-----------跳出2
                }
                if (e.hash == hash &&//和上面的操作一样,符合了该条件的话,说明,在这个链表中存储key和要插入的节点的key一样的节点,并且此时e指向这个节点
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;//跳出-----------跳出2
                p = e;//移动p指针,配合上面的  e = p.next 完成p的向后移动
            }
        }
        if (e != null) { // 如果满足条件,说明链表中存在了同样的key 
            V oldValue = e.value;//记录老value
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;//更新value
            afterNodeAccess(e);//空方法,这个是留给linkedHashMap实现的
            return oldValue;//返回老的value
        }
    }---------------------------------------------------------------------------------------------------------------------
    ++modCount;//修改的次数
    if (++size > threshold)//如果size大于了阈值(这个阈值负责的是是否扩容 默认threshold = cap * 0.75)
        resize();//扩容
    afterNodeInsertion(evict);//空方法,留给其他继承者去实现的
    return null;//返回null说明之前没有同样的key
}

resize()方法

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;//记录老的table
    int oldCap = (oldTab == null) ? 0 : oldTab.length;//记录老的table的容量  
    int oldThr = threshold;// 记录老的树化的阈值(容量*负载因子)
    int newCap, newThr = 0;//定义新的容量   新的树化的阈值
    if (oldCap > 0) {//如果老的容量大于0,就不是进行初始化操作---------------------------------------------------1
        if (oldCap >= MAXIMUM_CAPACITY) {//容量也不能无限大,当容量大于等于 最大的容量,则将树化的阈值赋值为最大容量
            threshold = Integer.MAX_VALUE;
            return oldTab;//直接返回,因为此时已经达到最大了,最多就是用完所有的容量,已经将树化的阈值调到最大了
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&//新的容量为老的容量的2倍,如果小于最大的容量大于默认容量
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // 将新的树化阈值调整为原来的2倍, 16(12)->32(24)->。。。。
    }
    else if (oldThr > 0) // 老的容量不大于0,但是老的树化阈值大于0的时候,将新的容量设置为老的树化的阈值----------------1
        newCap = oldThr;
    else {               // 老的容量不大于0,老的阈值不大0的时候,进行初始化---------------------------------------1
        newCap = DEFAULT_INITIAL_CAPACITY;//将新的容量设置为 16(默认的大小)
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//默认的树化的阈值 16 * 0.75
    }
    
    if (newThr == 0) {//如果新的树化阈值等于0,此时表示调用了HashMap的默认构造函数生成的对象,此时将树化阈值设置为ft和max的最小值
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;//将新的树化阈值赋值给  全局变量树化阈值
    /*        接下来进行老的数据搬运进新的位置中去           */
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;//完成新数组的声明
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {//遍历
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;//将搬运的老位置清空为null
                if (e.next == null)//这个桶中只有一个元素
                    newTab[e.hash & (newCap - 1)] = e;//直接将这个元素放入新桶的新的位置
                else if (e instanceof TreeNode)//如果是红黑树,则进行树的插入,与链表不同
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order  不是红黑树,进行插入的步骤
                    Node<K,V> loHead = null, loTail = null;//记录  与oldCap为0的节点
                    Node<K,V> hiHead = null, hiTail = null;//记录  与oldCap不为0的节点
                    Node<K,V> next;
                    do {//  do-while 进行链表的整体遍历移动到新数组中去
                        next = e.next;// next指向 e的后面一个
                        if ((e.hash & oldCap) == 0) {//将e链表中的元素分为  与oldCap 为0和不为0两种
                            if (loTail == null)//如果尾部为null说明 head肯定也是空
                                loHead = e;// 将 head赋值为e 
                            else//否则,将尾部的下一个  赋值 为 e
                                loTail.next = e;
                            loTail = e;//将尾部后移动
                        }
                        else {//记录  与oldCap不为0的节点
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);//将e指针指向 e的后面一个
                    if (loTail != null) {//将hash低位的节点  还是放置的在新数组的老index下
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {//将hash高位的节点,放置在新书的oldCap+j的index下
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;//返回新tab
}

如有错误,欢迎指出。

标签:hash,tab,阈值,树化,解读,key,null,逐行,HashMap
来源: https://blog.csdn.net/weixin_42496727/article/details/119062744

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有