详解HashMap

news2024/9/20 14:47:20

目录

1.hash code

2.数据结构

3.初始化

4.存取

4.1.put

4.2.get

5.迭代

6.扩容

7.JDK1.7版本存在的问题

7.1.性能跌落

7.2.循环链表

8.散列运算

9.扰动函数


1.hash code

hash code是使用hash函数运算得到的一个值,是对象的身份证号码,用于对象的辨重。在同一运行周期,对同一个对象多次调用hashcode(),只要是用于equals()的内容未变,那么每次得到的hash码也应该不变。,但不同运行周期间hash码可能会不同。

hash函数是将任意长度的输入通过散列算法变换成固定长度的输出的一个处理过程,hash函数只是个概念,其具体的实现各有不同。如果不同的输入产生了相同的输出,称为产生了“hash碰撞”,而优质的hash函数力求的就是不同的输入产生不同的输出,即减少碰撞。

2.数据结构

HashMap是个无序不可重复的键值映射,线程不安全

底层维护一个内部类——node(1.7叫Entry,1.8叫Node):

1.7及其以前版本,底层为数组+链表。1.8及其以后版本底层为数组+链表/红黑树。

允许key或者value为null,构造函数被调用的时候默认产生一个底层长度为16的Node类型的数组元素。

3.初始化

调用默认构造,不会实例化出数组,首次put操作时会开辟出一个长度为16的数组,也就是说hashmap的数组默认初始长度为16

可以调用相应构造函数在实例化的时候指定为其他值,官方要求值为2的N次方,如果传入的值不是2的N次方,会就该值向上取一个2的N次方数

4.存取

4.1.put

kv对存放进来的时候会被封装成Node类型,然后调用key的hashcode方法得到哈希码,

将这个hash码传到内置hash()中进行散列运算,得到一个散列值,然后将散列值与数组length进行运算最终得到该Node存放在位置的下标。

然后可能有两种情况:

1.如果该位置为null,直接放入。

2.如果该位置有node了,key不同,就直接挂在该node的后面,如果key相同,直接替换掉(map辨重的核心操作)

注意:

key相同,hashcode肯定相同,运算出的下标肯定也相同

key不同,hash函数可能产生碰撞,hashcode也可能相同,运算出来的下标可能也相同,

元素放入的顺序,在哈希表中被读出的顺序不一定相同。

4.2.get

根据传进来的key的hashcode方法产生的hash码做和put流程一样的散列运算然后和数组长度运算,得到该key应该所在的数组下标位置。实现了快速查找。

5.迭代

hashmap实现两种遍历方法:

1.keyset,返回值会封装在set里面,内含所有key。

2.entryset,所有entry会封装在set里面

 

6.扩容

触发扩容的前提是达到负载因子,即触发扩容的阈值,默认为0.75,即数组达到3/4的空间被使用就会触发扩容。hash桶(数组)会被扩容为原来的2倍。

扩容采用复制迁移的手段实现:

  1. 开辟出一个新数组。
  2. 将老数组上的所有元素rehash(),计算出一个新的hash值,然后对新数组length取模,计算出该元素应该在新数组上的存放位置。
  3. 迁移至新数组,迁移工作由transfer()方法完成。

7.JDK1.7版本存在的问题

7.1.性能跌落

当挂在一个位置上的节点过多,链表过长时会造成性能跌落。

7.2.循环链表

JDK1.7版本在扩容迁移时采用头插法,在并发的环境下会造成循环链表。

头插法:

从头开始取老数组上挂的链表,向新数组上挂时每次都挂在新数组链表的头部。

并发:

几个线程并发的访问同一个HashMap时,在几乎同一时间发现HashMap需要扩容,于是几个线程对同一个HashMap进行扩容迁移操作。

过程:

1.7的transfer方法,遍历原来table中每个位置的链表,并对每个元素进行重新hash,在新的newTable找到归宿,并插入。

 整个循环链表产生的过程如下:

原table:

 

 假设 线程2 刚刚执行到遍历指针e指向节点a, next指向节点b,时间片就用完了。

线程1继续执行,很不巧,a、b、c节点rehash之后又是在同一个位置7,开始移动节点、

 线程1完成工作后,线程2继续工作:

之前线程2的e指针记录到的应该迁移的节点为a,下一个应该迁移的节点为b。

 于是执行迁移后:

再往下执行,这时候头插法就会惹祸:

变量e又重新指回节点a,只能继续执行循环体,这里仔细分析下:

1、执行完Entry<K,V> next = e.next;,目前节点a没有next,所以变量next指向null;

2、e.next = newTable[i]; 其中 newTable[i] 指向节点b,那就是把a的next指向了节点b,这样a和b就相互引用了,形成了一个环;

解决办法:

JDK1.8版本,扩容迁移时采用尾插法避免循环链表。底层数据结构为数组+链表,链表节点数超过(包含)8时,链表转化为红黑树,回落到8以下时候,红黑树转化回链表。

8.散列运算

散列运算本身是指hash运算,一般hash运算过后对数组长度取余,得到存储位置,就是个完整的存储过程。计算机中直接求余效率不如位移运算,因此hashmap中用按位与代替了求余运算,整个过程如下:

hashmap中key自身调用hashcode(),生成hashcode,hashcode与数组的length-1展开成二进制做按位与运算。

hash%length==hash&(length-1)的前提是length是2的n次方,这也就是为什么要求hashmap数组的长度必须是2的N次幂。

因为2的n次方实际就是1后面n个0,2的n次方-1  实际就是n个1,所以使用2的n次方减1能尽可能的保留到更多位数的特质,可以更加保证均匀分布减少碰撞

例如长度为9时候,3&(9-1)=0  2&(9-1)=0 ,都在0上,碰撞了;

例如长度为8时候,3&(8-1)=3  2&(8-1)=2 ,不同位置上,不碰撞;

9.扰动函数

扰动函数的作用是使计算得到的hashCode值更加随机和分布均匀,减少哈希冲突的可能性,提高HashMap的查询效率。其中,^表示按位异或运算,>>>表示无符号右移运算。

在JDK1.7中散列函数的实现是:

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

在JDK1.8中散列函数的实现是:

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

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

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

相关文章

将项目从 SVN 迁移到 GIT

场景 项目开发中&#xff0c;项目原本是用的SVN&#xff0c;已经用了一年了&#xff0c;现在公司要抛弃SVN用Git&#xff0c;要求把SVN的代码直接搬过去Git&#xff0c;并保留之前的历史提交记录。 操作步骤 找到已经被svn管理的项目的根目录 WinFarm&#xff0c;右键 Git Ba…

手把手教你将微信小程序放到git上

背景 首先&#xff0c;要创建一个自己的git仓库&#xff0c;这里默认大家都能够自己创建了git仓库了。如果不会创建仓库的话&#xff0c;百度一下&#xff0c;很容易就能够创建了&#xff01;&#xff08;后续&#xff0c;如有不知道在哪里&#xff0c;怎么创建仓库的话&#…

群晖-第4章-Docker安装redis

群晖-第4章-Docker安装redis 本章介绍群晖docker安装redis的方法。如果你需要外网访问&#xff0c;可以参考我的群晖第1章。 参考 群晖使用 docker部署 Redis - 编程之家 Redis设置密码_惜惜然的博客-CSDN博客 在本地新建一个文本文件&#xff0c;命名为redis.conf&#xff…

GEE学习笔记 六十四:绿色中国报告(个人版)

2019年上半年在遥感圈里最火的一篇文章莫过于这篇《China and India lead in greening of the world through land-use management》&#xff08;China and India lead in greening of the world through land-use management | Nature Sustainability&#xff09;&#xff0c;…

Idea搭建Spring5.3.x源码阅读环境

1. 概述 Spring是一个轻量级Java开源框架&#xff0c;在Java项目开发过程中已经离不开Spring全家桶了&#xff0c;包括Spring、SpringBoot、SpringCloud等&#xff0c;学习好Spring基础源码也有助于更好在项目中使用Spring相关组件&#xff0c;在学习源码前需要搭建好源码学习…

二进制部署K8S

目录 一、环境准备 1、常见的k8s部署方式 2、关闭防火墙 3、关闭selinux 4、关闭swap 5、根据规划设置主机名 6、在master添加hosts 7、将桥接的IPv4流量传递到iptables的链 8、时间同步 二、部署etcd集群 1、master节点部署 2、查看证书的信息 2.1 创建k8s工作目…

使用useReducer + useContext 代替 react-redux

一. 概述 在 React16.8推出之前&#xff0c;我们使用react-redux并配合一些中间件&#xff0c;来对一些大型项目进行状态管理&#xff0c;React16.8推出后&#xff0c;我们普遍使用函数组件来构建我们的项目&#xff0c;React提供了两种Hook来为函数组件提供状态支持&#xff…

ccc-pytorch-基础操作(2)

文章目录1.类型判断isinstance2.Dimension实例3.Tensor常用操作4.索引和切片5.Tensor维度变换6.Broadcast自动扩展7.合并与分割8.基本运算9.统计属性10.高阶OP大伙都这么聪明&#xff0c;注释就只写最关键的咯1.类型判断isinstance 常见类型如下&#xff1a; a torch.randn(…

iOS开发:对苹果APNs远程推送原理的理解

本篇是对APNs推送原理的一个理解,希望看完后,能让你掌握一个知识点。 APNs是Apple Push Notification Service的缩写,也就是苹果的推送服务器。 远程通知的传递涉及几个关键组件: 您公司的服务器或第三方服务商,称为提供商服务器Apple 推送通知服务 (APNs)用户的设备您的…

Netty进阶实现自定义Rpc

项目地址&#xff1a;xz125/Rpc-msf (github.com)1 项目架构&#xff1a;RPC 框架包含三个最重要的组件&#xff0c;分别是客户端、服务端和注册中心。在一次 RPC 调用流程中&#xff0c;这三个组件是这样交互的&#xff1a;服务端(provider)在启动后会将它提供的服务列表和地址…

RocketMQ 第一章

RocketMQ 第一章 1、什么是MQ Message Queue&#xff08;消息队列&#xff09;&#xff0c;从字⾯上理解&#xff1a;⾸先它是⼀个队列。FIFO 先进先出的数据结构 —— 队列。消息队列就是所谓的存放消息的队列。 消息队列解决的不是存放消息的队列的⽬的&#xff0c;而是解…

AcWing1015.摘花生

AcWing 1015. 摘花生Hello Kitty想摘点花生送给她喜欢的米老鼠。她来到一片有网格状道路的矩形花生地(如下图)&#xff0c;从西北角进去&#xff0c;东南角出来。地里每个道路的交叉点上都有种着一株花生苗&#xff0c;上面有若干颗花生&#xff0c;经过一株花生苗就能摘走该它…

《FPGA学习》->蜂鸣器播放

&#x1f34e;与其担心未来&#xff0c;不如现在好好努力。在这条路上&#xff0c;只有奋斗才能给你安全感。你若努力&#xff0c;全世界都会为你让路。蜂鸣器的发声原理由振动装置和谐振装置组成&#xff0c;而蜂鸣器又分为无源他激型与有源自激型。本实验采用无源蜂鸣器&…

嵌入物理(PINN)还是基于物理(AD)?

文章目录1. 传统"反演问题"1.1 反演问题是什么1.2 常见反演问题1.3 传统反演问题的困境2. 深度学习优势3. AD inversion 例子3.1 ADsurf3.2 ADseismic关于PINN的内容大家可以直接google PINN (Physical-informed neural network),其主要的目的是用一个神经网络拟合物…

K8S 部署 Jenkins

本文使用 bitnami 镜像部署 Jenkins 官方文档&#xff1a;https://github.com/bitnami/charts/tree/main/bitnami/jenkins 添加 bitnami 仓库 helm repo add bitnami https://charts.bitnami.com/bitnami自定义 values.yaml storageClass&#xff1a;集群的存储类&#xff…

(考研湖科大教书匠计算机网络)第五章传输层-第八节1:TCP连接管理理论部分(三次握手与四次挥手)

获取pdf&#xff1a;密码7281专栏目录首页&#xff1a;【专栏必读】考研湖科大教书匠计算机网络笔记导航此部分内容借鉴博主【小林coding】 &#xff0c;其对计算机网络内容的图解可以说是深入浅出&#xff0c;尤其是三次握手和四次挥手这一部分&#xff0c;堪称全网最佳。所这…

OpenEuler安装软件方法

在树莓派上烧录好OpenEuler后上面是什么软件都没有的&#xff0c;像一些gcc的环境都需要自己进行配置。官方提供的安装命令是yum&#xff0c;但是执行yum是找不到命令的&#xff1a;   这个其实是因为OpenEuler中默认的安装软件使用了dnf而不是yum&#xff0c;所以软件的安装…

智能小车红外跟随原理

红外跟随电路红外跟随电路由电位器R17&#xff0c;R28&#xff1b;发光二极管D8&#xff0c;D9&#xff1b;红外发射管 D2&#xff0c;D4和红外接收管D3&#xff0c;D5和芯片LM324等组成,LM234用于信号的比较&#xff0c;并产生比较结果输出给单片机进行处理。智能小车红外跟随…

OpenGL学习日志之纹理

引言 为了使我们渲染的模型拥有更多细节&#xff0c;我们可以添加足够多的顶点&#xff0c;然后给每一个顶点都添加顶点颜色。但是这样就会产生很多额外的开销&#xff0c;因此就出现了纹理映射技术&#xff0c;我们通过纹理采样为物体的表面添加更多的细节。 纹理定义 通俗…

超25亿全球月活,字节依然没有流量

&#xff08;图片来源于网络&#xff0c;侵删&#xff09; 文|螳螂观察 作者| 搁浅虎鲸 注意看&#xff0c;这个男人叫梁汝波&#xff0c;是字节跳动的联合创始人&#xff0c;也是接棒张一鸣的新任CEO。 在字节跳动十周年之际&#xff0c;他发表了激情昂扬的演讲。“激发创…