HashMap的常见问题

Entry中的hash属性为什么不直接使用key的hashCode()返回值呢?

不管是JDK1.7还是JDK1.8中,都不是直接用key的hashCode值直接与table.length-1计算求下标的,而是先对key的hashCode值进行了一个运算,JDK1.7和JDK1.8关于hash()的实现代码不一样,但是不管怎么样都是为了提高hash code值与 (table.length-1)的按位与完的结果,尽量的均匀分布。

在这里插入图片描述

JDK1.7:

    final int hash(Object k) {int h = hashSeed;if (0 != h && k instanceof String) {return sun.misc.Hashing.stringHash32((String) k);}h ^= k.hashCode();h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);}

JDK1.8:

	static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}

虽然算法不同,但是思路都是将hashCode值的高位二进制与低位二进制值进行了异或,然高位二进制参与到index的计算中。

为什么要hashCode值的二进制的高位参与到index计算呢?

因为一个HashMap的table数组一般不会特别大,至少在不断扩容之前,那么table.length-1的大部分高位都是0,直接用hashCode和table.length-1进行&运算的话,就会导致总是只有最低的几位是有效的,那么就算你的hashCode()实现的再好也难以避免发生碰撞,这时让高位参与进来的意义就体现出来了。它对hashcode的低位添加了随机性并且混合了高位的部分特征,显著减少了碰撞冲突的发生。

HashMap是如何决定某个key-value存在哪个桶的呢?

因为hash值是一个整数,而数组的长度也是一个整数,有两种思路:

①hash 值 % table.length会得到一个[0,table.length-1]范围的值,正好是下标范围,但是用%运算效率没有位运算符&高。

②hash 值 & (table.length-1),任何数 & (table.length-1)的结果也一定在[0, table.length-1]范围。

在这里插入图片描述

JDK1.7:

static int indexFor(int h, int length) {// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";return h & (length-1); //此处h就是hash
}

JDK1.8:

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;if ((p = tab[i = (n - 1) & hash]) == null)  // i = (n - 1) & hashtab[i] = newNode(hash, key, value, null);//....省略大量代码
}
为什么要保持table数组一直是2的n次幂呢?

因为如果数组的长度为2的n次幂,那么table.length-1的二进制就是一个高位全是0,低位全是1的数字,这样才能保证每一个下标位置都有机会被用到。

举例1:

hashCode值是   ?
table.length是10
table.length-19????????
9	 00001001
&_____________00000000	[0]00000001	[1]00001000	[8]00001001	[9]一定[0]~[9]

举例2:

hashCode值是   ?
table.length是16
table.length-115????????
15	 00001111
&_____________00000000	[0]00000001	[1]00000010	[2]00000011	[3]...00001111    [15]范围是[0,15],一定在[0,table.length-1]范围内
解决[index]冲突问题

虽然从设计hashCode()到上面HashMap的hash()函数,都尽量减少冲突,但是仍然存在两个不同的对象返回的hashCode值相同,或者hashCode值就算不同,通过hash()函数计算后,得到的index也会存在大量的相同,因此key分布完全均匀的情况是不存在的。那么发生碰撞冲突时怎么办?

JDK1.8之间使用:数组+链表的结构。

在这里插入图片描述

JDK1.8之后使用:数组+链表/红黑树的结构。

在这里插入图片描述

即hash相同或hash&(table.lengt-1)的值相同,那么就存入同一个“桶”table[index]中,使用链表或红黑树连接起来。

为什么JDK1.8会出现红黑树和链表共存呢?

因为当冲突比较严重时,table[index]下面的链表就会很长,那么会导致查找效率大大降低,而如果此时选用二叉树可以大大提高查询效率。

但是二叉树的结构又过于复杂,占用内存也较多,如果结点个数比较少的时候,那么选择链表反而更简单。所以会出现红黑树和链表共存。

加载因子的值大小有什么关系?

如果太大,threshold就会很大,那么如果冲突比较严重的话,就会导致table[index]下面的结点个数很多,影响效率。

如果太小,threshold就会很小,那么数组扩容的频率就会提高,数组的使用率也会降低,那么会造成空间的浪费。

什么时候树化?什么时候反树化?
static final int TREEIFY_THRESHOLD = 8;//树化阈值
static final int UNTREEIFY_THRESHOLD = 6;//反树化阈值
static final int MIN_TREEIFY_CAPACITY = 64;//最小树化容量
  • 当某table[index]下的链表的结点个数达到8,并且table.length>=64,那么如果新Entry对象还添加到该table[index]中,那么就会将table[index]的链表进行树化。

  • 当某table[index]下的红黑树结点个数少于6个,此时,

    • 当继续删除table[index]下的树结点,最后这个根结点的左右结点有null,或根结点的左结点的左结点为null,会反树化
    • 当重新添加新的映射关系到map中,导致了map重新扩容了,这个时候如果table[index]下面还是小于等于6的个数,那么会反树化
package com.atguigu.map;public class MyKey{int num;public MyKey(int num) {super();this.num = num;}@Overridepublic int hashCode() {if(num<=20){return 1;}else{final int prime = 31;int result = 1;result = prime * result + num;return result;}}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;MyKey other = (MyKey) obj;if (num != other.num)return false;return true;}}
package com.atguigu.map;import org.junit.Test;import java.util.HashMap;public class TestHashMapMyKey {@Testpublic void test1(){//这里为了演示的效果,我们造一个特殊的类,这个类的hashCode()方法返回固定值1//因为这样就可以造成冲突问题,使得它们都存到table[1]中HashMap<MyKey, String> map = new HashMap<>();for (int i = 1; i <= 11; i++) {map.put(new MyKey(i), "value"+i);//树化演示}}@Testpublic void test2(){HashMap<MyKey, String> map = new HashMap<>();for (int i = 1; i <= 11; i++) {map.put(new MyKey(i), "value"+i);}for (int i = 1; i <=11; i++) {map.remove(new MyKey(i));//反树化演示}}@Testpublic void test3(){HashMap<MyKey, String> map = new HashMap<>();for (int i = 1; i <= 11; i++) {map.put(new MyKey(i), "value"+i);}for (int i = 1; i <=5; i++) {map.remove(new MyKey(i));}//table[1]下剩余6个结点for (int i = 21; i <= 100; i++) {map.put(new MyKey(i), "value"+i);//添加到扩容时,反树化}}
}
key-value中的key是否可以修改?

key-value存储到HashMap中会存储key的hash值,这样就不用在每次查找时重新计算每一个Entry或Node(TreeNode)的hash值了,因此如果已经put到Map中的key-value,再修改key的属性,而这个属性又参与hashcode值的计算,那么会导致匹配不上。 HUANGANHE

这个规则也同样适用于LinkedHashMap、HashSet、LinkedHashSet、Hashtable等所有散列存储结构的集合。

JDK1.7中HashMap的循环链表是怎么回事?如何解决?

在这里插入图片描述

避免HashMap发生死循环的常用解决方案:

  • 多线程环境下,使用线程安全的ConcurrentHashMap替代HashMap,推荐
  • 多线程环境下,使用synchronized或Lock加锁,但会影响性能,不推荐
  • 多线程环境下,使用线程安全的Hashtable替代,性能低,不推荐

HashMap死循环只会发生在JDK1.7版本中,主要原因:头插法+链表+多线程并发+扩容。

在JDK1.8中,HashMap改用尾插法,解决了链表死循环的问题。

补:

  1. JDK7当插入数据达到容量*负载因子时,会对底层数组进行扩容,然后再通过头插法进行数据插入,在多线程的情况下,会出现循环链表的情况;
  2. JDK8则是在通过尾插法进行数据插入之后,再对底层数组进行扩容,在多线程的情况下,也能避免链表死循环的问题。

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

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

相关文章

c++ 指针总结

概述 内存地址 在计算机内存中&#xff0c;每个存储单元都有一个唯一的地址(内存编号)。通俗理解&#xff0c;内存就是房间&#xff0c;地址就是门牌号 指针和指针变量 指针&#xff08;Pointer&#xff09;是一种特殊的变量类型&#xff0c;它用于存储内存地址。指针的实质…

机器学习_PySpark-3.0.3随机森林回归(RandomForestRegressor)实例

机器学习_PySpark-3.0.3随机森林回归(RandomForestRegressor)实例 随机森林回归 (Random Forest Regression): 任务类型: 随机森林回归主要用于回归任务。在回归任务中, 算法试图预测一个连续的数值输出, 而不是一个离散的类别。 输出: 随机森林回归的输出是一个连续的数值,…

算力租赁费用包括哪些

相比于企业自购设备、自建机房、自己运营&#xff0c;服务器租赁是绝大数企业的首先&#xff0c;租赁服务器从一定程度上解决了企业资金预算不足、AI芯片难买的局面。 随着文生视频大模型Sora、大语言模型Grok-1的相继出现&#xff0c;对高新能算力资源和服务的需求不断提高&a…

暴力枚举法

虽然暴力枚举法有时候效率低&#xff0c;时间复杂度高&#xff0c;但是在面对小规模数据集的时候&#xff0c;暴力枚举法往往是很好的思维利器。 B: 01 串的熵&#xff08;5分&#xff09; 问题描述 #include <iostream> #include <cmath> #include <algorithm…

什么是云HIS?云HIS的优点是什么?云HIS适用于什么医院?

什么是云HIS&#xff1f;云HIS的优点是什么&#xff1f;云HIS适用于什么医院&#xff1f; 一、什么是云HIS&#xff1f; 云HIS系统是一个运用云计算、大数据、物联网等新兴信息技术的业务和技术平台。它旨在按照现代医疗卫生管理要求&#xff0c;以数字化形式提供医疗卫生行业…

Mybatis generate xml 没有被覆盖

添加插件即可 <plugin type"org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>

Andorid OTA A/B升级

参考 A/B&#xff08;无缝&#xff09;系统更新 升级后的显著变化是 ro.build.date.utc 、和 版本号 发生变化。 ro.odm_dlkm.build.dateThu Mar 28 01:09:45 UTC 2024 ro.odm_dlkm.build.date.utc1711588185ro.odm.build.dateThu Mar 28 01:09:45 UTC 2024 ro.odm.build.dat…

排序算法-桶排序

桶排序是一种基于计数的排序算法&#xff0c;它的核心思想是将待排序的元素分到不同的桶中&#xff0c;然后对每个桶中的元素进行排序&#xff0c;最后将所有桶中的元素依次取出来就得到了有序的结果。 具体的实现步骤如下&#xff1a; 创建一个固定大小的桶数组&#xff0c;…

epic免费游戏在哪里领 epic免费游戏怎么领取 图文教程一看就会

Epic Games是一家位于美国北卡罗来纳州卡里的视频游戏和软件开发商&#xff0c;由Tim Sweeney于1991年创立。该公司最著名的作品包括《堡垒之夜》和虚幻引擎&#xff0c;后者是一种广泛用于游戏开发的商用游戏引擎。Epic Games在2020年和2024年分别与索尼和迪士尼达成财务合作及…

ARM架构麒麟操作系统安装配置Mariadb数据库

、安装配置JDK (1)检查机器是否已安装JDK 执行 java -version命令查看机器是否安装JDK,一般麒麟操作系统默认安装openjdk 1.8。 (2)安装指定版本JDK 如果麒麟操作系统默认安装的openjdk 1.8不符合需求的话,可以卸载机器安装的openjdk 1.8并按需安装所需的openjdk版本…

Mybatis 执行批量插入

首先,创建一个简单的 insert 语句: <insert id”insertname”>insert into names (name) values (#{value}) </insert>然后在 java 代码中像下面这样执行批处理插入: list < string > names new arraylist(); names.add(“fred”); names.add(“barney”)…

#esp8266模块通过AT指令获取网络时间(苏宁时间)

一、IDE&#xff1a;keil、cubemx、Arduino......... 二、记录&#xff1a; 1.依次发送以下指令&#xff08;发送新行&#xff09; AT ATCWMODE1 ATCWDHCP1,1 ATCWJAP"Redmi K40 Gaming","87654321" ATCIPSTART"TCP","quan.suning.com&quo…

Leetcode110_平衡二叉树

1.leetcode原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 2.题目描述 给定一个二叉树&#xff0c;判断它是否是 平衡二叉树 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;…

RabbbitMQ

初识MQ 同步通讯和异步通讯 什么是同步通讯呢&#xff1f;举个例子&#xff0c;你认识了一个小姐姐&#xff0c;聊的很火热&#xff0c;于是你们慢慢开始打电话&#xff0c;视频聊天&#xff0c;这种方式就成为同步通讯&#xff0c;那什么是一部通讯呢&#xff0c;同样的&…

【性能测试】接口测试各知识第3篇:Jmeter 基本使用流程,学习目标【附代码文档】

接口测试完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;接口测试&#xff0c;学习目标学习目标,2. 接口测试课程大纲,3. 接口学完样品,4. 学完课程,学到什么,5. 参考:,1. 理解接口的概念。学习目标&#xff0c;RESTFUL1. 理解接口的概念,2.什么是接口测试…

python知识点汇总(十一)

python知识点总结 1、当Python退出时&#xff0c;是否会清除所有分配的内存&#xff1f;2、Python的优势有哪些&#xff1f;3、什么是元组的解封装4、Python中如何动态获取和设置对象的属性&#xff1f;5、创建删除操作系统上的文件6、主动抛出异常7、help() 函数和 dir() 函数…

在vue3中使用pinia

在vue3项目中使用pinia 使用vite创建项目和安装pinia依赖 npm init vitelatest npm i pinia从pinia包中解构出defineStore函数创建store片段,这里有一个要注意的点是第一个参数是这个store的id,第二个参数类比vue2中的script内容,state对应data,actions对应methods import {…

mybatis05:复杂查询:(多对一,一对多)

mybatis05&#xff1a;复杂查询&#xff1a;&#xff08;多对一&#xff0c;一对多&#xff09; 文章目录 mybatis05&#xff1a;复杂查询&#xff1a;&#xff08;多对一&#xff0c;一对多&#xff09;前言&#xff1a;多对一 &#xff1a; 关联 &#xff1a; 使用associatio…

Java开发环境安装

Java开发环境安装 1、JDK安装2、Maven安装3、MySQL安装4、Tomcat安装5、Nodejs安装 1、JDK安装 java环境搭建问题之——此环境变量太大。此对话框允许将值设置为最长2047个字符。“ 解决方法: Windows下Java环境配置教程 2、Maven安装 Maven配置教程 3、MySQL安装 mysql8…

机器学习和深度学习常见算法

监督学习算法对比 线性回归&#xff08;Linear Regression&#xff09; vs 支持向量机&#xff08;Support Vector Machine, SVM&#xff09; 线性回归&#xff1a; 特点&#xff1a;简单、易于理解和实现&#xff0c;基于线性假设建立输入和输出之间的关系。应用场景&#…