java中HashMap原理

article/2025/11/9 13:12:04

1、为什么用HashMap?

HashMap是一个散列桶(数组和链表),它存储的内容是键值对(key-value)映射HashMap采用了数组和链表的数据结构,能在查询和修改方便继承了数组的线性查找和链表的寻址修改HashMap是非synchronized,所以HashMap很快HashMap可以接受null键和值,而Hashtable则不能(原因就是equlas()方法需要对象,因为HashMap是后出的API经过处理才可以)2、HashMap的工作原理是什么?

HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,计算并返回的hashCode是用于找到Map数组的bucket位置来储存Node 对象。这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,作为Map.Node 。

以下是HashMap初始化 ,简单模拟数据结构Node[] table=new Node[16] 散列桶初始化,tableclass Node { hash;//hash值 key;//键 value;//值 node next;//用于指向链表的下一层(产生冲突,用拉链法)}

以下是具体的put过程(JDK1.8版)1、对Key求Hash值,然后再计算下标

2、如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的Hash值相同,需要放到同一个bucket中)

3、如果碰撞了,以链表的方式链接到后面

4、如果链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表

5、如果节点已经存在就替换旧值

6、如果桶满了(容量16*加载因子0.75),就需要 resize(扩容2倍后重排)

以下是具体get过程(考虑特殊情况如果两个键的hashcode相同,你如何获取值对象?)当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。

 

3、有什么方法可以减少碰撞?

 

扰动函数可以减少碰撞,原理是如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这就意味着存链表结构减小,这样取值的话就不会频繁调用equal方法,这样就能提高HashMap的性能。(扰动即Hash方法内部的算法实现,目的是让不同对象返回不同hashcode。)使用不可变的、声明作final的对象,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择。为什么String, Interger这样的wrapper类适合作为键?因为String是final的,而且已经重写了equals()和hashCode()方法了。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。4、HashMap中hash函数怎么是是实现的?

我们可以看到在hashmap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置。如何计算这个位置就是hash算法。前面说过hashmap的数据结构是数组和链表的结合,所以我们当然希望这个hashmap里面的元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表。 所以我们首先想到的就是把hashcode对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大的,能不能找一种更快速,消耗更小的方式,我们来看看JDK1.8的源码是怎么做的(被楼主修饰了一下)

static final int hash(Object key) {if (key == null){ return 0; } int h; h=key.hashCode();返回散列值也就是hashcode // ^ :按位异或 // >>>:无符号右移,忽略符号位,空位都以0补齐 //其中n是数组的长度,即Map的数组部分初始化长度 return (n-1)&(h ^ (h >>> 16));}

简单来说就是

1、高16bt不变,低16bit和高16bit做了一个异或(得到的HASHCODE转化为32位的二进制,前16位和后16位低16bit和高16bit做了一个异或)

2、(n·1)&hash=->得到下标

5、拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?

之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。

6、说说你对红黑树的见解?

1、每个节点非红即黑

2、根节点总是黑色的

3、如果节点是红色的,则它的子节点必须是黑色的(反之不一定)

4、每个叶子节点都是黑色的空节点(NIL节点)

5、从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

7、解决hash 碰撞还有那些办法?

开放定址法。

当冲突发生时,使用某种探查技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定的地址。

按照形成探查序列的方法不同,可将开放定址法区分为线性探查法、二次探查法、双重散列法等。

下面给一个线性探查法的例子  

问题:已知一组关键字为(26,36,41,38,44,15,68,12,06,51),用除余法构造散列函数,用线性探查法解决冲突构造这组关键字的散列表。

解答:为了减少冲突,通常令装填因子α由除余法因子是13的散列函数计算出的上述关键字序列的散列地址为(0,10,2,12,5,2,3,12,6,12)。

前5个关键字插入时,其相应的地址均为开放地址,故将它们直接插入T[0],T[10),T[2],T[12]和T[5]中。

当插入第6个关键字15时,其散列地址2(即h(15)=15%13=2)已被关键字41(15和41互为同义词)占用。故探查h1=(2+1)%13=3,此地址开放,所以将15放入T[3]中。

当插入第7个关键字68时,其散列地址3已被非同义词15先占用,故将其插入到T[4]中。

当插入第8个关键字12时,散列地址12已被同义词38占用,故探查hl=(12+1)%13=0,而T[0]亦被26占用,再探查h2=(12+2)%13=1,此地址开放,可将12插入其中。

类似地,第9个关键字06直接插入T[6]中;而最后一个关键字51插人时,因探查的地址12,0,1,…,6均非空,故51插入T[7]中。

8、如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。这个值只可能在两个地方,一个是原下标的位置,另一种是在下标为<原下标+原容量>的位置

9、重新调整HashMap大小存在什么问题吗?

当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。(多线程的环境下不使用HashMap)。

10.为什么多线程会导致死循环,它是怎么发生的?

HashMap的容量是有限的。当经过多次元素插入,使得HashMap达到一定饱和度时,Key映射位置发生冲突的几率会逐渐提高。这时候,HashMap需要扩展它的长度,也就是进行Resize。1.扩容:创建一个新的Entry空数组,长度是原数组的2倍。2.ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。


http://chatgpt.dhexx.cn/article/pvOktGd0.shtml

相关文章

HashMap的实现原理

1. HashMap概述 HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作&#xff0c;并允许使用null值和null键。此类不保证映射的顺序&#xff0c;特别是它不保证该顺序恒久不变。 在java编程语言中&#xff0c;最基本的结构就是两种&#xff0c;一个…

软件测试用例分析和用例设计

测试用例的概念 测试用例&#xff08;test case&#xff09;&#xff0c;也叫测试案例&#xff0c;是为了达到一个最佳的测试效果或者高效的发现软件中的隐藏错误&#xff08;缺陷&#xff09;而精心设计的包括场景步骤和数据。 通用的定义&#xff1a;是关于一个功能验证时候…

软件测试用例设计练习

1、完成163邮箱注册用例的编写 邮箱地址&#xff1a;6~18个字符&#xff0c;可使用字母、数字、下划线&#xff0c;需要以字母开头 密码&#xff1a;8~16个字符&#xff0c;大、小写字母、数字、标点符号&#xff0c;3种或以上组合 2、需求&#xff1a;输入三条边&#xff…

软件测试用例详细规范

软件测试用例详细规范 为什么编写测试用例详细测试用例模板测试用例字段介绍用例操作步骤用例预期结果&#xff1a; 测试用例录入原则&#xff1a;测试用例设计步骤测试用例案例&#xff1a;测试用例校验点&#xff1a; 为什么编写测试用例 我也不知道&#xff0c;自己百度 详…

软件测试——测试用例设计方法

1、测试用例定义 测试用例又叫test case&#xff0c;是为某个特殊目标而编制的一组测试输入&#xff0c;执行条件以及预期结果&#xff0c;以便测试某个程序路径或核实是否满足某个特定需求。 2、测试用例的特性 有效性&#xff1a;测试用例能够被使用&#xff0c;且被不同人…

接口测试用例怎么写?

测试流程 需求规格说明书--测试计划--测试用例--用例评审--开发 接口文档--接口分析--接口用例设计--评审--接口测试执行&#xff08;关注数据库&#xff09;--前后端对接 系统界面测试--测试结束 如何设计接口测试用例&#xff1f; 1.接口正常调用&#xff0c;先要能跑通…

软件测试 - 用例篇

回顾上一篇博客主要内容:软件测试 - 基础篇 1: 软件测试的流程是什么? 需求分析,测试计划,测试设计/测试开发,测试执行,测试报告 需求分析 分析需求,验证需求的正确性和合理性,从需求中提取出测试项 测试计划 要考虑测试人数,测试环境,测试时间,测试设备等 测试设计/测试开发 …

软件测试用例优先级,软件测试用例的优先级划分方法

随着互联网的不断发展&#xff0c;程序员对于软件品质以及运行状况等参数关注程度也在提高&#xff0c;而今天我们就一起来了解一下&#xff0c;在划分测试用例优先级的时候都有哪些划分方法可以使用。 没有软件系统是完美的&#xff0c;任何系统都有BUGS。但是每一次得迭代都有…

软件测试:测试用例

一、通用测试用例八要素  1、用例编号&#xff1b;  2、测试项目&#xff1b;  3、测试标题&#xff1b;  4、重要级别&#xff1b;  5、预置条件&#xff1b;  6、测试输入&#xff1b;  7、操作步骤&#xff1b;  8、预期输出 二、具体分析通用测试用例八要…

【软件测试】测试用例的设计方法

文章目录 1. 测试用例的概念2. 设计测试用例的好处3. 基于需求设计测试用例3.1 功能性需求3.2 非功能性需求 4. 设计测试用例的具体方法4.1 等价类4.2 边界值4.3 错误猜测法4.4 场景设计法4.5 因果图法4.6 正交法 5. 测试用例的粒度 1. 测试用例的概念 测试用例就是测试人员向…

如何写好测试用例

目录 前言 为什么要写用例&#xff1f; 那怎么写好测试用例呢&#xff1f; 那么我们日常测试中&#xff0c;如果用xmind梳理用例结构注意哪些点呢&#xff1f; 结语 前言 经历过校招或社招的测试同学&#xff0c;都会被问到测试用例的设计、使用方法&#xff0c;以及用例的…

软件测试——测试用例设计测试分类详解

文章目录 1. 测试用例的基本要素2. 测试用例的设计方法2.1 基于需求设计测试用例2.11 功能性需求测试分析2.12 非功能性需求测试分析 2.2 具体的设计测试用例的方法等价类&#xff08;非常重要&#xff09;边界值错误猜测法场景法因果图法正交法 3. 测试分类3.1 按照测试对象划…

软件测试-如何写好测试用例

软件测试-如何写好测试用例 一、课程介绍前置知识点 二、测试用例与编写流程介绍测试用例介绍需求分析与测试点编写测试用例编写注意 三、 测试用例编写&#xff0c;评审与管理测试用例编写方法3-2 慕课网注册功能测试用例编写 (13:25)3-3 慕课网搜索&#xff0c;APP下载功能测…

测试用例应该怎么写

一、背景 有些测试同学&#xff0c;写测试用例的时候&#xff0c;直接就是将需求文档上的内容抄一遍&#xff0c;转换成测试用例的格式。没有加入任何自己的思考和理解&#xff0c;没有融入任何测试方法论。测试完全依赖于需求文档的质量&#xff0c;依赖于产品经理保姆级的服…

【软件测试】测试用例设计

目录 &#x1f337;1. 测试用例的基本要素 &#x1f337;2. 测试用例的设计方法 &#x1f333;2.1 基于需求进行测试用例的设计 ⭐️&#xff08;1&#xff09;功能需求测试分析 ⭐️&#xff08;2&#xff09;非功能需求测试分析 &#x1f333;2.2 具体的设计方法 &#…

软件测试用例设计规范

文章目录 1 目的2 规范内容2.1 设计原则2.1.1 可执行性2.1.2 可维护性2.1.3 可代表性2.1.4 可判定性 2.2 必要元素2.2.1 用例包和用例对象名命2.2.2 测试目的2.2.3 测试优先级2.2.4 测试环境2.2.5 前提条件2.2.6 后置关联2.2.7 用例状态 2.3 综合策略2.3.1 必要的边界值分析2.3…

软件测试——测试用例

目录 1.测试用例的基本要素 2.测试用例的设计方法 2.1基于需求的设计方法&#xff08;Requirements-Based Testing&#xff0c;RBT&#xff09; 2.2等价类划分法 2.3边界分析法 2.4因果图 2.5正交排列 2.6场景设计法 2.7错误猜测法…

软件测试(测试用例)—写用例无压力

软件测试——用例篇 文章目录 软件测试——用例篇一、概念二、测试用例总体设计方案1、等价类 ☆2、边界值 ☆2.1 边界值法设计用例步骤 3、判定表 ☆4、因果图5、场景设计法 ☆6、错误猜测法7、正交排列三、实际操作中注意的点3.1测试用例的注意点 四、缺陷介绍1、缺陷的判定标…

软件测试用例

测试用例 为什么要写测试用例测试用例的基本要素QQ登录的测试用例功能正常时异常时 界面易用性可移植性性能 具体的设计测试用例的方法等价类边界值错误猜测法场景设计法因果图法正交排列 测试用例的有效性 为什么要写测试用例 测试用例是测试执行的依据测试用例可以复用&…

软件测试用例概述

软件测试用例概述 知识点 什么是测试用例如何获取需求的测试点测试用例的模板测试用例的优先级测试用例的设计原则测试用例的维护 简介 软件测试是软件质量管理最有效的方法之一&#xff0c;同时也是耗时最多的一项工作&#xff0c;基于时间因素的考虑&#xff0c;软件测试…