从本文开始,介绍一下最常用的一个集合对象HashMap,HashMap存储的是键值对,本文采用的基于JDK11的源码实现。 一般大家都知道HashMap是通过put操作把一组键值对(key和value)存储到HashMap中,然后可以通过get(key)去获取key对应的value。而最重要的这两个过程是怎么实现的呢?下面我们就来对put和get这两个过程做一个分析。
HashMap基本工作原理
下面先看一段源码:
/** * The table, initialized on first use, and resized as * necessary. When allocated, length is always a power of two. * (We also tolerate length zero in some operations to allow * bootstrapping mechanics that are currently not needed.) */ transient Node<K,V>[] table;
当用户调用put方法的时候把key和value放入到HashMap的时候,这个数组table就是实际存储key和value的地方。HashMap把用户传入的key和value封装成一个Node<K,V>对象,把该Node<K,V>对象放入到table对应的位置。Map执行get操作的时候,并没有传入具体的数组的索引位置信息,只是传入了key,因此这个地方就会涉及到一个key转索引的一个操作,然后根据索引获取table中对应位置的Node对象,把value值返回给用户。由于数组的访问时间复杂度是O(1),因此Map的get操作也可以认为是O(1)( 这个地方先暂时理解为O(1),具体原因见后面)。
简单来说,在执行put方法的时候,Map会根据传入的key获取它hashcode值,然后根据hashcode与table大小进行求模运算,得到的值就是它在table数组索引位置。实际这个过程又有点复杂,具体下面开始分析。
HashMap 数组寻址与hash值计算
用户通过key访问map获取value的时候,原理是用key的hash值来与数组的大小取模获取数组的索引。但实际在HashMap实现中,对取模运算进行了一下优化,采用了(n-1) & hash(key)的方法获取数组索引,这里的n是table的大小,hash(key)表示key的哈希值,这种方法可以得到与取模运算一样的效果,但是速度要比取模运算快。
下面看一下,hash(key)的实现逻辑
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
从上面的源码看:
这里大家比较好奇,为什么会进行这种复杂操作,他的用意是什么?下面来给大家说一下这个过程。
假设 table的大小是16,key1和Key2调用hashCode方法获取的值的二进制形式分别是:
1111 1111 1111 1101 0000 0000 0000 0001 # key1 1111 1111 1111 1111 0000 0000 0000 0001 # key2
首先我们直接使用key1和key2的hashCode获取的值去计算在的table的索引值。
具体过程是:
# key1在table中索引的计算过程与结果 1111 1111 1111 1101 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 1111 & #n-1的二进制 --------------------------------------- 0000 0000 0000 0000 0000 0000 0000 0001 # 得到的table索引是1 # key2在table中索引的计算过程与结果 1111 1111 1111 1111 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 1111 & #n-1的二进制 --------------------------------------- 0000 0000 0000 0000 0000 0000 0000 0001 #得到的table索引是1
根据上面计算结果可知,虽然key1和key2值不同,但是最后得到的table的索引都是1,这样就会出现了冲突。主要原因是在与n-1进行&操作的时候,通常n的值比较小,因此高16位都是0,这样0和任何数&结果都是0。通常key的hashCode取值很不固定。从最高位到最低位都会出现1的可能。比如key1和key2,他们的区别恰恰是出现在自己的hashCode的高16位,因此key1和key2与n-1进行&操作的结果是一样的。如果key1和key2经过hash()方法处理后呢,来看看结果:
# key1在table中索引的计算过程与结果 1111 1111 1111 1101 0000 0000 0000 0001 #key1本身 ^ 0000 0000 0000 0000 1111 1111 1111 1101 #key1右移16的值 ----------------------------------------------- 1111 1111 1111 1111 1111 1111 1111 1100 # hash(key1)计算后的值 & 0000 0000 0000 0000 0000 0000 0000 1111 #n-1的二进制 ----------------------------------------------- 0000 0000 0000 0000 0000 0000 0000 1100 #得到的table索引是12 # key2在table中索引的计算过程与结果 1111 1111 1111 1111 0000 0000 0000 0001 #key2本身 ^ 0000 0000 0000 0000 1111 1111 1111 1111 #key2右移16的值 ----------------------------------------------- 1111 1111 1111 1111 1111 1111 1111 1110 #hash(key1)计算后的值 & 0000 0000 0000 0000 0000 0000 0000 1111 #n-1的二进制 ----------------------------------------------- 0000 0000 0000 0000 0000 0000 0000 1110 #得到的table索引是14
这样key1和key2不会出现位置冲突。当key和自己的高16位进行异或操作的后的值的低16位中同时保留了原始key低16位和高16位的特征。因此key1和key2再和n-1进行&运算时,减少了出现相同值的可能性。明白了这些内容内容,下一篇文章开始结束HashMap的put和get方法的实现原理。
以上就是Java HashMap实现原理分析(一)的详细内容,更多关于Java HashMap原理的资料请关注小牛知识库其它相关文章!
本文向大家介绍JavaScript定时器实现的原理分析,包括了JavaScript定时器实现的原理分析的使用技巧和注意事项,需要的朋友参考一下 JavaScript中的定时器大家基本在平时的开发中都遇见过吧,但是又有多少人去深入的理解其中的原理呢?下面我们就来分析一下定时器的实现原理。 一、储备知识 在我们在项目中一般会遇见过这样的两种定时器,第一种是setTimeOut,第二种是setInter
类装载器ClassLoader 类装载器工作机制 类装载器就是寻找类的节码文件并构造出类在JVM内部表示对象的组件。在Java中,类装载器把一个类装入JVM中,要经过以下步骤: [1.]装载:查找和导入Class文件; [2.]链接:执行校验、准备和解析步骤,其中解析步骤是可以选择的: [2.1]校验:检查载入Class文件数据的正确性; [2.2]准备:给类的静态变量分配存储空间; [2.3]解
本文向大家介绍iOS实现消息推送及原理分析,包括了iOS实现消息推送及原理分析的使用技巧和注意事项,需要的朋友参考一下 一、消息推送原理: 在实现消息推送之前先提及几个于推送相关概念,如下图1-1: 1、Provider:就是为指定IOS设备应用程序提供Push的服务器,(如果IOS设备的应用程序是客户端的话,那么Provider可以理解为服务端[消息的发起者]); 2、APNS:Apple Pu
本文向大家介绍AQS 原理分析 ?相关面试题,主要包含被问及AQS 原理分析 ?时的应答技巧和注意事项,需要的朋友参考一下 AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
本文向大家介绍单机redis分布式锁实现原理解析,包括了单机redis分布式锁实现原理解析的使用技巧和注意事项,需要的朋友参考一下 最近我们有个服务经常出现存储的数据出现重复,首先上一个系统流程图: 用户通过http请求可以通知任务中心结束掉自己发送的任务,这时候任务中心会通过MQ通知结束服务去结束任务保存数据,由于任务结束数据计算保存有一定延时,所以存在用户短时间内多次结束同一个任务,这时候就会
本文向大家介绍PHP对象链式操作实现原理分析,包括了PHP对象链式操作实现原理分析的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了PHP对象链式操作实现原理。分享给大家供大家参考,具体如下: 什么是链式操作呢?使用jQuery的同学印象应该会很深刻.在jQuery中,我们经常会这样的来操作DOM元素: 连贯操作看起来的确很酷,也非常的方便代码的阅读.那么在PHP里面是否可以实现呢?答案是肯
本文向大家介绍java乐观锁原理与实现案例分析,包括了java乐观锁原理与实现案例分析的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了java乐观锁原理与实现。分享给大家供大家参考,具体如下: 简单说说乐观锁。乐观锁是相对于悲观锁而言。悲观锁认为,这个线程,发生并发的可能性极大,线程冲突几率大,比较悲观。一般用synchronized实现,保证每次操作数据不会冲突。乐观锁认为,线程冲突可能
本文向大家介绍纯js实现遮罩层效果原理分析,包括了纯js实现遮罩层效果原理分析的使用技巧和注意事项,需要的朋友参考一下 可以说这个功能,在我理解了前面的“贪吃蛇”之后,实在是与刚开始想象的难度差了好多,当然是这种方式有取巧之嫌,终归是实现了功能,我们来进行分析整理 1、实现原理 本片文章的 是实现原理如下: * 实际上弹出层、遮罩层和原页面显示分别为三个不同的div * 弹出层的层级在遮罩层之上,