HashMap源码分析以及面试题

news2024/10/6 2:32:20

目录

5、HashMap源码分析

5.1、初始化容量

5.2、负载因子是多少?

5.3、负载因子可以大于或小于0.75吗?

5.4、扩容长度为多少?

5.5、下标是怎么计算的? 

5.6、hash冲突,是怎么解决的?

5.7、什么时候转换为树呢?

5.8、HashMap的数据结构


5、HashMap源码分析

5.1、初始化容量

在没有进行put方法之前,容量为0,put方法之后,会赋值默认的初始化容量为16。

源码中,初始化HashMap无参构造,会赋值一个负载因子,为0.75。但并没有赋值初始化容量。

那么此时初始化容量为0

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
public class HashMapDemo {
    public static void main(String[] args) {
        HashMap hashMap=new HashMap();
        //调用put方法
        hashMap.put("a","1");
    }
}

 put源码

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

此时会返回一个putVal方法,里面传递的是key的hash值和key值以及value值。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //tab是一个数组,初始化为null,走第一个if语句
        if ((tab = table) == null || (n = tab.length) == 0)
            //这里调用了一个resize()方法,也就是扩容方法
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

resize()扩容方法 

 final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //HashMap初始化时oldTab为null,经过三目运算为0,那么不会走下面的if语句
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            //最终会走这里的赋值,将newCap容量赋值为默认的容量16
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        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;
                    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;
                        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;
    }

 由上面源码可知,HashMap在没有调用put方法之前,初始化容量为0,掉用put方法之后为16。

5.2、负载因子是多少?

0.75,也称扩容阈值

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

5.3、负载因子可以大于或小于0.75吗?

不可以, 因为在扩容的时候,会通过负载因子*初始化容量,默认是16*0.75=12,也就是说当容量大于12的时候,会进行扩容原来2倍。

但是负载因子小于0.75的时候,比如变为0.5,那么当16*0.5=8的时候就要进行扩容,那么会导致资源浪费。

如果负载因子大于0.75,比如16*1=16,那么会导致查询和插入时候的次数增多,性能会下降。

5.4、扩容长度为多少?

每次扩容为原来的2倍

        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE; //2的30次方
                return oldTab;
            }
            //如果没有超过最大值,则扩大容量为原来的2倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // 2的一次方 2倍
        }

5.5、下标是怎么计算的? 

(数组的长度-1)与 key的hash值进行位运算

i = (n - 1) & hash

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)
            n = (tab = resize()).length;
        //计算HashMap的下标
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

5.6、hash冲突,是怎么解决的?

hash冲突指的是,在HashMap的底层数组中,在新加入的数据中,发现它与之前存在的数据具有相同的hash值,但key不一样。数组中一个内存地址只能存储一个数据,那么怎么实现存储多个呢?当两个key的hash值一样,且两个key的equals为false的时候,使用链表或者红黑树。


5.7、什么时候转换为树呢?

               else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }

p.next会从索引为1的位置,一直循环遍历下去,直到为7的时候, 满足了binCount>=TREEIFY_THRESHOLD-1的条件,去尝试着转换为树 ,因为索引为7的时候满足,也就是链表的长度为8的时候,所以说当链表长度大于等于8的时候,还没有完,因为是尝试着去转换为树,所以查看treeifBin方法

final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        //如果数组为null或者数组长度小于64,则进行扩容
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        //否则,就会转换为树
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }
 static final int MIN_TREEIFY_CAPACITY = 64;

可以看出,源码中,当数组长度大于等于64的时候,才会走下面的转换数的代码。

总结:当链表的长度大于等于8并且数组的长度大于等于64的时候,链表才会转换为树。

5.8、HashMap的数据结构

在jdk1.8之前,hashMap的底层数据结构为:数组+链表

在jdk1.8之后,hashMap的数据结构为:数组+链表+红黑树

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/83443.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]JAVA毕业设计英语课程学习网站(系统+LW)

[附源码]JAVA毕业设计英语课程学习网站&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术…

安装 NVSwitch GPU 服务器的 cuda 驱动版本、nvidia-docker 指南

一&#xff0c;安装 Cuda 驱动 可参考笔者之前写过的文章&#xff1a; 升级 GPU 服务器 cuda 驱动版本指南 如果出现如下报错&#xff0c;则需安装 gcc、kernel-devel&#xff0c;请参考下面第二步安装 gcc、kernel-devel。 二&#xff0c;安装 gcc、kernel-devel 1&#…

基于狮群算法优化的lssvm回归预测-附代码

基于狮群算法优化的lssvm回归预测 - 附代码 文章目录基于狮群算法优化的lssvm回归预测 - 附代码1.数据集2.lssvm模型3.基于狮群算法优化的LSSVM4.测试结果5.Matlab代码摘要&#xff1a;为了提高最小二乘支持向量机&#xff08;lssvm&#xff09;的回归预测准确率&#xff0c;对…

[附源码]计算机毕业设计高校车辆管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis MavenVue等等组成&#xff0c;B/S模式…

地表最强:免费文字转语音工具

0、前言 我们在刷短视频的时候&#xff0c;经常会听到一些AI合成声音&#xff0c;它们有各种音色、语调&#xff0c;甚至不同的情绪&#xff0c;听起来与人声无异&#xff0c;其实这些大都是利用微软Azure的文字转语音技术来实现的。 虽然国内也有很多配音工具&#xff0c;但…

【优化算法】粒子群优化算法

粒子群优化算法粒子群优化算法简介粒子群优化算法原理粒子群优化算法的数学描述粒子群优化算法框架PySwarms&#xff1a;Python中粒子群优化的研究工具包PySwarms快速使用示例&#xff1a;编写自己的优化循环相关资料粒子群优化算法简介 粒子群优化算法(Particle Swarm Optimi…

如何安然度过行业大萧条,听听10年测试老鸟的分析

国内的互联网行业发展较快&#xff0c;所以造成了技术研发类员工工作强度比较大&#xff0c;同时技术的快速更新又需要员工不断的学习新的技术。因此淘汰率也比较高&#xff0c;超过35岁的基层研发类员工&#xff0c;往往因为家庭原因、身体原因&#xff0c;比较难以跟得上工作…

Huawei Compute Architecture for Neural Networks - CANN

Huawei Compute Architecture for Neural Networks - CANN1. Technical support (技术支持) https://support.huawei.com/enterprise/en/index.html https://support.huawei.com/enterprise/zh/index.html Server - Intelligent Computing -> Ascend Computing (昇腾计算)…

ChatGPT简介与Q群机器人部署教程

最近OpenAi推出的ChatGPT火出NLP圈子&#xff0c;看到不少人说强人工智能已经出现&#xff0c;于是赶紧来体验感受一下。 ChatGPT简介 ChatGPT是一个基于GPT-3&#xff08;通用语言模型&#xff09;的聊天机器人。它的目的是能够通过自然语言交流与用户进行对话。它能够理解用…

Seq2Seq基本原理

Seq2Seq基本原理 encoder & decoder Seq2Seq结构用于多个输入和多个输出的模型&#xff0c;但是输入和输出的大小可能并不一致&#xff0c;其本质上也是RNN网络的一个扩展&#xff0c;常见的应用场景包括&#xff1a;机器翻译、语音识别、文本摘要等。 常见的seq2seq的输…

Linux源码下载

方法一、Git下载源码 1.1、进入Linux官网 通过任意浏览器访问&#xff1a;https://www.kernel.org/&#xff0c;界面如下 1.2、选择版本 点击某一个版本对应的browse&#xff0c;Linux版本分为&#xff1a;mainline&#xff08;主线版本&#xff09;、stable&#xff08;稳定版…

函数之公式求和

【问题描述】 编写一个按以下公式求和的函数&#xff0c; 其中 a 是一个 2 ~ 8 的数字。 例如&#xff0c;S(3,5)333333333333333。 在主函数中&#xff0c;由键盘输入a和n&#xff0c;输出 S(a-1,n-1)S(a,n)S(a1,n1) 的值。 例如输入&#xff1a;3 5 则输出&#xff1a…

PAI-Diffusion模型来了!阿里云机器学习团队带您徜徉中文艺术海洋

作者&#xff1a;汪诚愚、段忠杰、朱祥茹、黄俊 导读 近年来&#xff0c;随着海量多模态数据在互联网的爆炸性增长和训练深度学习大模型的算力大幅提升&#xff0c;AI生成内容&#xff08;AI Generated Content&#xff0c;AIGC&#xff09;的应用呈现出爆发性增长趋势。其中…

算法竞赛入门【码蹄集进阶塔335题】(MT2251-2270)

算法竞赛入门【码蹄集进阶塔335题】(MT2251-2270&#xff09; 文章目录算法竞赛入门【码蹄集进阶塔335题】(MT2251-2270&#xff09;前言为什么突然想学算法了&#xff1f;为什么选择码蹄集作为刷题软件&#xff1f;目录1. MT2251 讲价2. MT2252 复数类13. MT2253 复数类24. MT…

CF GLR24-C. Doremy‘s City Construction

CF原题链接 题目大意&#xff1a;n个结点&#xff0c;每个结点有一个正数值。现在让你在n个点间进行边的连接&#xff0c;唯一限制条件是不能出现这种情况&#xff1a;如3个点A,B,C&#xff0c;且A<B<C&#xff0c;那么不能同时出现边&#xff08;A&#xff0c;B&#xf…

冯诺依曼体系和操作系统概念

Ⅰ. 冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。 截至目前&#xff0c;我们所认识的计算机&#xff0c;都是有一个个的硬件组件组成 输入单元&#xff1a;包括键盘, 鼠标&#xf…

太详细了,在 Windows 上安装 PySpark 保姆级教程

在本文中&#xff0c;我将和大家一起学习如何在 Windows 上安装和运行 PySpark&#xff0c;以及如何使用 Web UI 启动历史服务器和监控 Jobs。 安装 Python 或 Anaconda 发行版 https://www.python.org/downloads/windows/ 从 Python.org 或 Anaconda 发行版 下载并安装 Pyth…

在win10和docker下安装DVWA

win10安装 DVWA下载地址https://github.com/digininja/DvWA/archive/master.zip 在phpstudy软件包下的www文件夹中新建一个文件夹名为dvwa&#xff0c;把DVWA-master压缩包解压进去 删除/dvwa/config文件夹下的config.inc.php.dist文件的.dist后缀 打开config.inc.php&#x…

接口测试(四)—— Requests库发送请求、Cookie、Session、UnitTest管理测试用例

目录 一、Requests库 1、Requests库安装和简介 1.1 简介 1.2 安装 1.3 查验 2、设置http请求语法 2.1 案例1 2.2 案例2 2.3 案例3 2.4 案例4 2.5 案例5 3、Cookie 3.1 Cookie简介 3.2 CookieSession认证方式 3.3 案例 4、Session 4.1 Session简介 4.2 Sessi…

Flask从入门到放弃二(请求与相应、Session的使用与源码分析、Flash闪现、异步说明、请求拓展、BluePrint蓝图)

文章目录一、请求与响应1&#xff09;请求对象2&#xff09;响应对象3&#xff09;前后端分离和混合二、Session的使用和原理1&#xff09;Session的使用2&#xff09;Session源码分析三、Flash闪现四、异步说明五、请求扩展六、BluePrint蓝图一、请求与响应 1&#xff09;请求…