HashMap详细讲解-面试题大全

news2024/11/24 22:57:59

HashMap底层数据结构是什么?1.7和1.8有何不同

        1.7是 数组 + 链表,1.8 是数组 + (链表 或者 红黑树)

        当链表的元素比较多的时候,链表就会转换成红黑树,红黑树的元素减少了,红黑树也会转换成链表

为什么要优化?

        Map这种集合容器,最主要的应用就是想通过一个key最快的时间找到对应的value,事实上这个时间复杂度接近为O(1),那么怎么样才能实现这么快的速度呢?于是就引入了数组,数组可以理解为内存中一块连续的内存空间,且每一小块空间都有自己的索引,通过这个索引就能直接找到对应空间的值。

        这也是哈希表的优点:快速查找

        可以实现集合中元素的快速查找,可以通过计算元素的哈希码,对其向桶的个数取模运算(源码不是取模,但是类似取模),得到它的桶下标,有了桶下标,就能定位它的位置,接下来只需要进行少量的比较,最优的情况下只需要进行1次比较,就可以找到这个元素。

发生了哈希冲突

        比如某些人通过找到你的hash碰撞值,来让你的HashMap不断地产生碰撞,那么相同key位置的链表就会不断增长,当你需要对这个HashMap的相应位置进行查询的时候,就会去循环遍历这个超级大的链表,性能及其地下。java8使用红黑树来替代超过8个节点数的链表后,查询方式性能得到了很好的提升,从原来的是O(n)到O(logn)。

        在最坏情况下,链表的长度是n,就是遍历n次才能找到元素,所以,链表的时间复杂度为O(n)。

        当插入的元素哈希码对桶长度取模后都在一个链表时,查询的速度也会变慢很多,这时候就需要优化。

 

 

解决哈希冲突

如果冲突后是链表,判断该链表是否大于8,如果大于8并且数组容量小于64,就进行数组扩容;

如果链表节点大于8并且数组的容量大于64,则将这个结构转换为红黑树

转换成红黑树的两个条件(树化)

红黑树:节点左侧的比之小,右侧比之大。用红黑树查询就会快。

1. 链表的长度超过一个阈值,这个阈值固定是8,也就是只有链表的长度大于8才会优化成红黑树

2. 数组的长度必须 >= 64,如果数组的长度不够大,首先会考虑用数组扩容的方式来优化,也就是增加了桶的个数,数组一扩容,就会重新根据哈希码值来进行插入到不同的桶里面。

什么时候红黑树 退化 为链表

退化情况1:在扩容时如果拆分树时,树元素个数

退化情况2: remove树节点时,若root、root.left、root.right、root.left.left 有一个为null,也会退化为链表 

为什么先用链表,再转红黑树?

在解决hash冲突的时候选择先用链表,再转红黑树

  1. 链表短的时候是性能比红黑树是高的,只有链表长的时候性能才不如红黑树
  2. 链表的底层是Node,红黑树是TreeNode,TreeNode里面的成员变量多,内存占用多

        因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。当元素小于8个的时候,此时做查询操作,链表结构已经能保证查询性能。当元素大于8个的时候,红黑树搜索时间复杂度是O(logn),而链表是O(n),此时需要红黑树来加快查询速度,但是新增节点的效率变慢了。因此,如果-开始就用红黑树结构,元素太少,新增效率又比较慢,无疑这是浪费性能的。

索引怎么计算?

原始hash -- 》 二次hash --》索引

  1. 在java中所有的对象继承自Object,而Object类里面有一个方法:hashCode,因为任何一个对象都有一个hashcode, 这个就是原始hash值,调用HashMap中的hash() 哈希方法将原始哈希值 转换成 二次hash。
  2. 二次哈希的结果,再和数组容量“取模”运算,得到的就是索引,也就是桶下标

注意:源码中不是用取模hash % n ,而是用hash & (n - 1) ,计算结果一样,但是后者性能更高。

例如:97 % 16 = 1,97 & (16 - 1) = 1,结果是一样的。

 

为什么要二次哈希:hash()?

        在计算索引的时候,不会直接用hashcode去计算,而是先进行 hash() 方法,根据hashcode 计算出 二次hash后再计算索引。

        目的:二次hash()是为了综合高位数据,让哈希分布更为均匀,分布的越均匀,链表就不会过长

这个计算二次哈希 也就是 扰动

1.7里面的 hash()

1.8 里面的 hash()

解决hash冲突的办法有哪些

        如果冲突后是链表,判断该链表是否大于8,如果大于8并且数组容量小于64,就进行扩容;

        如果链表节点大于8并且数组的容量大于64,则将这个结构转换为红黑树;否则,链表插入键值对,若key存在,就覆盖掉value.

        解决Hash冲突方法有:开放定址法、再哈希法、链地址法(拉链法)、建立公共溢出区。 HashMap中采 用的是链地址法。

        1、开放定址法也称为再散列法,基本思想就是,如果p=H(key)出现冲突时,则以p为基础,再次hash,I p1=H(p) ,如果p1再次出现冲突,则以p1为基础,以此类推,直到找到一个不冲突的哈希地址pi。因此开放定址法所需要的hash表的长度要大于等于所需要存放的元素,而且因为存在再次 hash,所以只能在删除的节点上做标记,而不能真正删除节点。

        2、再哈希法(双重散列,多重散列), 提供多个不同的hash函数, 当R1=H1(key1)发生冲突时,再计算R2=H2(key1),直到没有冲突为止。这样做虽然不易产生堆集,但增加了计算的时间。

        3、链地址法(拉链法), 将哈希值相同的元素构成一个同义词的单链表并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除 的情况。

        4、建立公共溢出区,将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统-放到溢出

一般用什么作为HashMap的key?

一般用Integer、 String这种不可变类当HashMap当key,而且String最为常用。

●因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就是HashMap中的键往往都使用字符串的原因。

●因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的,这些类已经很规范的重写了hashCode(以及equals()方法。

put方法流程?1.7与1.8有何不同?

流程

  1. HashMap是懒惰创建数组的,首次使用才创建数组
  2. 计算索引(桶下标)
  3. 如果桶下标还没人占用,创建Node占位返回
  4. 如果桶下标已经有人占用
    1. 已经是TreeNode走红黑树的添加或更新逻辑
    2. 是普通Node,走链表的添加或更新逻辑,如果链表长度超过树化阈值,走树化逻辑
  5. 返回前检查容量是否超过阈值,一旦超过进行扩容

区别

  1. 链表插入节点时,1.7是头插法,1.8是尾插法
  2. 1.7是大于等于阈值且没有空位时才扩容,而1.8是大于阈值就扩容
  3. 1.8在扩容计算Node索引时,会优化

总结 1.7 和 1.8 的区别

底层:

  • HashMap在1.7中是由 数组+链表 也可以说 哈希表+链表 实现的
  • HashMap在1.8中是由 数组+链表 +红黑树 (链表长度大于8,并且数组长度大于了64之后,转换为红黑树,红黑树的查找效率更快, O(logn))
  • 在JDK1.7中,HashMap存储的是Entry对象 在JDK1.8当中,HashMap存储的是实现Entry接口的Node对象

put方法:

  • JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法
  • 为什么从头插法改成尾插法? 在1.7中,是没有红黑树的 在并发的情况下单链表过长,会成环,发生死循环 在1.8中,尾插法就可以解决这个问题

扩容机制: 扩容因子0.75 扩容倍数2倍 初始容量16

  • 在JDK1.7的时候是先扩容后插入的,扩容过程中会将原来的数据,放入到新的数组中,但是会重新计算hash值进行分配,这样就会导致无论这一次插入是不是发生hash冲突都需要进行扩容,如果这次插入的并没有发生Hash冲突的话,那么就会造成一次无效扩容, 但是在1.8的时候是先插入再扩容的,优点是可以减少1.7的一次无效的扩容,因为如果这次插入没有发生Hash冲突的话,那么其实就不会造成扩容

加载(负载)因子为何默认是0.75f

基础篇-51-HashMap_负载因子为何是0.75f_哔哩哔哩_bilibili

  1. 在空间占用与查询时间之间取得较好的权衡
  2. 大于这个值,空间节省了,但链表就会比较长影响性能
  3. 小于这个值,冲突减少了,但扩容就会更频繁,空间占用多

多线程下操作HashMap会有啥问题?

扩容死链(1.7)

基础篇-53-HashMap_并发扩容死链(1.7)_哔哩哔哩_bilibili

数据结乱(1.7,1.8)

key 能否为null,作为key的对象有什么要求?

HashMap的key可以为null,但 Map的其他实现则不然

作为key的对象,必须实现 hashCode和equals,并且key的内容不能修改(不可变)

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

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

相关文章

前后分离的优势

1.可以实现真正的前后端解耦,前端服务器使用nginx。 前端/WEB服务器放的是css,js,图片等等一系列静态资源(甚至你还可以css,js,图片等资源放到特定的文件服务器,例如阿里云的oss,并使…

【计算机网络】前后端分离,HTTP协议,网络分层结构,TCP,强缓存/协商缓存

❤️ Author: 老九 ☕️ 个人博客:老九的CSDN博客 🙏 个人名言:不可控之事 乐观面对 😍 系列专栏: 文章目录 前后端分类HTTP协议HTTP组成HTTP的版本HTTP的请求方式HTTP请求头HTTP响应头强缓存和协商缓存 HT…

Linux ls -l输出文件信息详解

在linux中,我们知道一切皆为文件,经常我们会使用ls -l去查看文件的信息,今天会大家详细讲解一下ls -l输出的文件属性信息。 1.ls -l输出 命令: ls -l 通过ls -l命令输出,我们可以看到上图中的属性信息输出&#xff…

【珍藏版】生态系统NPP及碳源、碳汇模拟、土地利用变化、未来气候变化、空间动态模拟

由于全球变暖、大气中温室气体浓度逐年增加等问题的出现,“双碳”行动特别是碳中和已经在世界范围形成广泛影响。碳中和可以从碳排放(碳源)和碳固定(碳汇)这两个侧面来理解。陆地生态系统在全球碳循环过程中有着重要作…

五、JSP05 分页查询及文件上传

五、JSP 分页查询及文件上传 5.1 使用分页显示数据 通过网络搜索数据时最常用的操作,但当数据量很大时,页面就会变得冗长,用户必须拖动才能浏览更多的数据 分页是把数据库中需要展示的数据逐页分步展示给用户 以分页的形式显示数据&#xff…

Elasticsearch 8.X “图搜图”实战

1、什么是图搜图? "图搜图"指的是通过图像搜索的一种方法,用户可以通过上传一张图片,搜索引擎会返回类似或者相关的图片结果。这种搜索方式不需要用户输入文字,而是通过比较图片的视觉信息来找到相似或相关的图片。这项…

Tomcat服务器的安装即相关介绍

一、Tomcat的安装步骤 1、访问官网下载点击此处进入Tomcat官网; 2、在下图所示位置点击想要下载的版本下载,这边演示的是以Tomcat8为演示对象; 3、进入下载页面如下图所示,根据系统类型和版本选择合适的安装包; 4、下…

Wampsever升级增加php5.6的方法过程

1、下载wampserver2.5,文件包名:wampserver2.5-Apache-2.4.9-Mysql-5.6.17-php5.5.12-64b.exe https://sourceforge.net/projects/wampserver/files/WampServer%202/Wampserver%202.5/ 这个版本只有40M,包含: Apache-2.4.9&#x…

SpringCloud OpenFeign 学习

SpringCloud OpenFeign 文章目录 SpringCloud OpenFeign1 OpenFeign介绍2 OpenFeign-应用实例3 OpenFeign 测试 1 OpenFeign介绍 OpenFeign 是个声明式 WebService 客户端,使用 OpenFeign 让编写 Web Service 客户端 更简单 它的使用方法是定义一个服务接口然后在上…

【Java 抽象类抽象方法】什么是抽象类方法,如何定义,起什么作用?

博主:_LJaXi Or 東方幻想郷 专栏: Java | 从入门到入坟 Java 抽象类 & 抽象方法 抽象类的概念 👅抽象方法的概念 🐬抽象类和抽象方法结合使用 🦄 Java中的抽象类和抽象方法是面向对象编程中的重要概念,…

【系统学习】Java基础4之lamda表达式和函数式接口

lamda表达式与函数式接口 lamda表达式 语法格式一:无参,无返回值 Lambda 需要一个参数,但是没有返回值 语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断” 语法格式四:…

MySQL和Redis之间的存储区别

概述 MySQL是一种关系型数据库,而Redis是一种键值对存储数据库。虽然它们都是用来存储和管理数据的,但是它们在很多方面都有不同,但是它们在存储策略、日志存储方式、硬盘存储、数据恢复等方面都有一定的区别。 数据类型 MySQL支持多种数据…

电脑怎么通过网络传输文件?

可以通过网络在电脑之间传输文件吗? “由于天气的原因,我的老板决定让所有员工在家工作。但是我很多工作文件都在公司的电脑中,怎么才能将公司的文件远程传输到我家里的电脑上?电脑可以通过网络远程传输文件吗?” …

计算机网络 | I/O模型、网络模型(OSI七层及TCP/IP四层)

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和…

Python快速实现网页工具的利器

Python快速实现网页工具的利器 Streamlit是一个基于Python的Web应用程序开发框架,它具有快速开发、交互式、易于使用等特点。使用Streamlit,开发者可以很容易地将Python代码转换为漂亮的、交互式的Web应用程序,无需繁琐的前端开发经验。 如果…

什么是浪涌保护器SPD

浪涌保护器(SPD),也称为电涌保护器,是为各种电子设备,仪器和通信线路提供安全保护的电子设备。当由于外部干扰在电路或通信电路中突然产生尖峰电流或电压时,浪涌保护装置可以在很短的时间内传导和分流&…

设计模式之~策略模式

策略模式(Strategy): 它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。 结构图: 实例: 优点: 策略模式…

MySQL行锁浅析

概述 MySQL是非常流行的关系型数据库,许多系统都使用它来存储和管理数据。在高并发环境下,为了保证数据的一致性和可靠性,数据库需要实现并发控制,其中包括锁机制。MySQL提供了两种锁类型,一种是表级锁,另…

Java遍历Map集合,获取key、value等方式

首先构建一个Map集合&#xff1a; Map<String, Integer> buynew HashMap<>();buy.put("苹果手机", 2);//添加键值对buy.put("智能手表", 1);buy.put("java书", 1);buy.put("c语言书", 1);buy.put("西瓜", 2);打…

Git已经在本地提交过文件了,但又给撤销了,恢复已经撤销的内容

Git&#xff0c;我已经在本地提交过文件了&#xff0c;也就是已经执行了git commit -m "xxx"&#xff0c;但提交完之后又给撤销了&#xff0c;撤销的还能回来嘛&#xff1f; 这种情况仍然有机会恢复它。撤销提交的方法取决于你撤销提交的方式。 说白了就是&#xff0…