首页 > 编程语言 >HashMap源码

HashMap源码

时间:2023-08-01 12:44:26浏览次数:36  
标签:Node hash HashMap int 源码 key null 节点

put方法

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;  // 这个p是开始定位到的Node或者TreeNode
    if ((tab = table) == null || (n = tab.length) == 0)   // 如果数组是null或者长度是0
        n = (tab = resize()).length;  // 先扩容,然后获取数组长度
    if ((p = tab[i = (n - 1) & hash]) == null)  // 获取要存储位置的第一个元素是null,说明该位置是空的
        tab[i] = newNode(hash, key, value, null);  // 创建一个新的节点对象放在该位置  【添加元素位置1】
    else {  // hash冲突,位置上有数据了
        Node<K,V> e; K k;  // 这个e就是最终找到的节点
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))  // 第一个的hash值和要存的数据hash相同,而且key相同
            e = p;  // 说明要找的就是这个元素
        else if (p instanceof TreeNode)  // 如果第一个节点类型是TreeNode,说明已经变成红黑树了
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);  // 执行红黑树的查找流程
        else {  // 不是红黑树,第一个也不是,就要按照链表继续往下找了
            for (int binCount = 0; ; ++binCount) {  // 循环遍历链表
                if ((e = p.next) == null) { // 下一个节点是null,也就是之前没有存过这个数据,后面当前没有其他节点了
                    p.next = newNode(hash, key, value, null);  // 创建一个新元素放到链表中下一个节点位置就行了    【添加元素位置2】
                    if (binCount >= TREEIFY_THRESHOLD - 1) // 判断链表长度是不是达到变成红黑树的阈值了
                        treeifyBin(tab, hash);  // 变成红黑树
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))  // 找到了之前存的这个数据
                    break;
                p = e; // 指向下一个节点继续查找
            }
        }
        if (e != null) { // 找到了对应的节点
            V oldValue = e.value;  // 存一下旧的值
            if (!onlyIfAbsent || oldValue == null) // 允许覆盖或者值是null
                e.value = value;  // 设置新值
            afterNodeAccess(e);  // 后置处理,空方法
            return oldValue;  // 返回旧值
        }
    }
    // 如果是新添加的元素会执行到这里,上面的【添加元素位置1】【添加元素位置2】
    ++modCount;
    if (++size > threshold)  // 判断容量是否超过阈值
        resize();  // 扩容
    afterNodeInsertion(evict);  // 后置处理,空方法
    return null;  // 返回null
}

1、数组是不是空,是空的话先扩容;

2、根据hash定位到数组的索引位置,如果是null,直接创建节点添加到该位置;

3、如果不是null,类型是TreeNode,说明已经变成红黑树,按照红黑树的方式进行查找;

4、如果是普通Node类型,就按照链表进行查找;

扩容resize方法

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;  // 旧容量,如果没有初始化就是0,有数据就是数组长度
    int oldThr = threshold;  // 扩容阈值
    int newCap, newThr = 0;
    if (oldCap > 0) {  // 旧容量 > 0
        if (oldCap >= MAXIMUM_CAPACITY) { // 旧容量大于最大值
            threshold = Integer.MAX_VALUE; // 阈值更新为最大
            return oldTab; // 不能扩容了,直接返回旧数组
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)  // 新容量为旧容量的2倍 小于最大容量 而且 旧容量达到初始化阈值
            newThr = oldThr << 1; // double threshold  // 新容量为旧容量的2倍
    }
    else if (oldThr > 0)  // 旧容量为0,旧阈值大于0
        newCap = oldThr;  // 新容量为旧阈值
    else {                // new HashMap()第一次put会执行这里
        newCap = DEFAULT_INITIAL_CAPACITY;  // 默认初始化容量 16
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);  // 默认初始化阈值 12
    }
    if (newThr == 0) {
        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;  // 重新hash分配到新的数组中
                else if (e instanceof TreeNode)  // 如果是树节点
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order 保存链表顺序
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

红黑树插入

final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                               int h, K k, V v) {
    Class<?> kc = null;
    boolean searched = false;
    TreeNode<K,V> root = (parent != null) ? root() : this;  // 找到根节点
    for (TreeNode<K,V> p = root;;) {
        int dir, ph; K pk;
        if ((ph = p.hash) > h)  // 要插入的hash值比根节点小
            dir = -1;
        else if (ph < h) // 要插入的hash值比根节点大
            dir = 1;
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))  // 要插入的hash值等于根节点,而且key相同,说明就是根节点
            return p;
        else if ((kc == null &&  // 第一次默认是null,后面是String
                  (kc = comparableClassFor(k)) == null) ||  // k的类型是不是实现了Comparable接口,k一般都是String
                 (dir = compareComparables(kc, k, pk)) == 0) {// 1、判断pk == null,也就是root节点的key是不是null,一般不会存null,不符合	            												// 2、判断pk的类型是不是kc,也就是root节点的key类型是不是String,一般都符合
            											      // 3、符合2的话,比较k.compareTo(pk),前面的if判断已经知道两个key是不相等的
            											      // 也就是发生了hash冲突,所以这里一定不是0,不太懂这里的逻辑
            if (!searched) {  // 初始值为false
                TreeNode<K,V> q, ch;
                searched = true;
                if (((ch = p.left) != null &&  // 左子树不为null
                     (q = ch.find(h, k, kc)) != null) ||  // 左子树找到了
                    ((ch = p.right) != null &&  // 右子树不为null
                     (q = ch.find(h, k, kc)) != null))  // 右子树找到了
                    return q;  // 找到了就返回
            }
            dir = tieBreakOrder(k, pk); // hash冲突也要有个顺序,相等放在左边,dir=-1
        }

        TreeNode<K,V> xp = p;  // 根节点
        if ((p = (dir <= 0) ? p.left : p.right) == null) {  // dir小于等于0表示比根节点小,放在左子树,dir>0表示比根节点大,放在右子树
            												// 子树遍历到头了,那就插入新节点,否则重新循环
            Node<K,V> xpn = xp.next;
            TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);  // 创建一个树节点
            if (dir <= 0)
                xp.left = x;  // 放在左边
            else
                xp.right = x;  // 放在右边
            xp.next = x;  // 记录链表关系
            x.parent = x.prev = xp;  // 记录树节点的parent和链表的prev
            if (xpn != null)
                ((TreeNode<K,V>)xpn).prev = x;  // 记录链表关系
            moveRootToFront(tab, balanceInsertion(root, x));  // 平衡操作,旋转
            return null;  // 插入新节点,返回null
        }
    }
}

tableSizeFor方法

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

如果指定了初始容量,就按照这个方法计算出不小于指定容量的最小的2的幂作为初始容量。

基本思路:

先把指定的cap减个1,计算完再加个1,目的是为了防止cap本身就是2的幂,最后算出来的变成了cap的2倍,比如指定的是4,最后计算出来的是8;

假设初始值为0001xxxxxxxxxxx,x表示不确定0还是1

右移1位按位取或操作,得到结果00011xxxxxxxxxxxx,最左侧有效位变为两个1

右移2位按位取或操作,得到结果0001111xxxxxxxxxxx,最左侧有效位变为4个1

依次类推,右移4位变为8个1,右移8位变为16个1,右移16位变为32个1

因为是int类型的,所以最长32位,最多移动16位就把有效位全部变为1了,最后再加1,就变成2的n次幂了

标签:Node,hash,HashMap,int,源码,key,null,节点
From: https://www.cnblogs.com/bright1st/p/17596151.html

相关文章

  • 智慧校园源码:vue2+Java+springboot+MySQL+elmentui+jpa+jwt
    智慧校园综合管理云平台源码系统主要以校园安全、智慧校园综合管理云平台为核心,以智慧班牌为学生智慧之窗,以移动管理平台、家校沟通为辅。教师—家长一学校—学生循环的无纸化管理模式及教学服务,实现多领域的信息互联互通以及校园管理一体化、信息数据化、数据自动化。智慧班牌融合......
  • 国标GB28181视频平台LntonGBS(源码版)国标平台迁移服务器后无法启动的问题解决方案
    国标视频云服务LntonGBS支持设备/平台通过国标GB28181协议注册接入,并能实现视频的实时监控直播、录像、检索与回看、语音对讲、云存储、告警、平台级联等功能。平台部署简单、可拓展性强,支持将接入的视频流进行全终端、全平台分发,分发的视频流包括RTSP、RTMP、FLV、HLS、WebRTC等格......
  • 推荐魔娃西游源码
     演示地址:runruncode.com/shouyou/19503.html手游魔娃西游是一款受欢迎的手机游戏。这款游戏以中国古代神话故事《西游记》为背景,玩家可以扮演主角孙悟空,与其他角色一起展开冒险旅程。在游戏中,玩家将面临各种挑战和任务。他们需要完成各种任务,如打败恶魔、解救被困的角色以......
  • RocketMq消费原理及源码解析
    消费原理概览 先简单说下常见的rocketMq的部署方式,上图中broker为真正计算和存储消息的地方,而nameServer负责维护broker地 图中右侧consumemessage部分即是本文重点描述的部分,主要分为ConsumerGroup和Consumer,consumerGroup可以参考https://rocketmq.apache.org/docs/do......
  • php随机图片源码
    <?php//设置图片文件夹路径$dir='images/';//获取文件夹中所有图片文件名$files=glob($dir.'*.{jpg,jpeg,png,gif}',GLOB_BRACE);//随机选择一张图片$random_file=$files[array_rand($files)];//设置响应头为图片类型header('Content-Type:image/jpeg');//输出......
  • 从源码解读Mysql 5.7性能和数据安全性的提升
    下面我们从源码来分析mysql的事务提交以及事务在何时将binlog复制到从库的。MYSQL_BIN_LOG::ordered_commit,这个是事务在binlog阶段提交的核心函数,通过该函数,实现了事务日志写入binlog文件,以及触发dump线程将binlog发送到slave,在最后的步骤,将事务设置为提交状态。我们来分析MYSQL_B......
  • HashMap深入学习
    1.HashMap和HashTable的区别?a.HashMap是线程不安全的,HashTable是线程安全的。b.HashTable不允许有null键和null值。c.HashMap底层是数组+链表+红黑树,而HashTable底层是数组+链表。d.HashMap默认的初始大小为16,每次扩容变为原来的2倍;HashTable默认初始大小为11,每次扩容后容量变为原......
  • Nacos源码 (1) 源码编译及idea环境
    本文介绍从gitee下载nacos源码,在本地编译,并导入idea进行本地调试。从gitee下载源码由于github访问速度慢,所以我选择使用gitee的镜像仓库:gitclonehttps://gitee.com/mirrors/Nacos.git本文使用2.0.2版本,所以需要切换到2.0.2分支:cdNacosgitcheckout2.0.2创建一个自己......
  • 某交易平台客服系统源码搭建部署
    近期我公司与宁德鸿凯网络科技有限公司达成合作,为其产品鸿凯交易平台等搭建客服系统,提供全源码客服系统搭建部署。项目需求客户公司的客服比较多,有四五十个,需求是能够将客服系统用于公司多部门下的用户咨询接待,多部门的客服坐席智能自动分配接待,并且能互相转接用户等。后期,需......
  • 视频直播网站源码,随机密码生成器
    视频直播网站源码,随机密码生成器方法调用 publicstaticvoidmain(String[]args){    //排除字符0OoB81lI,包含大写字母,包含小写字母,包含数字,包含特殊字符,长度8,生成10000个,特殊字符集    generatePassword("0OoB81lI",true,true,true,true,8,10000,"~!@^*%......