HASHMAP的原理分析

HashMap实现原理和源码详细分析 JDK1.8 HashMap实现原理和源码详细分析,学习要点:知道HashMap的数据结构。了解HashMap中的散列算法。知道HashMap中put、remove、get的代码实现。HashMap的哈希冲突是什么?怎么处理的?知道HashMap的扩容机制 阅读详情

Java HashMap 核心源码解读

本篇对HashMap实现的源码进行简单的分析。
所使用的HashMap源码的版本信息如下:

/*
* @(#)HashMap.java 1.73 07/03/13
*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/

一.概述

在Java中每一个对象都有一个哈希码,这个值可以通过hashCode()方法获得。hashCode()的值和对象的equals方法息息相关,是两个对象的值是否相等的依据,所以当我们覆盖一个类的equals方法的时候也必须覆盖hashCode方法。

例如String的hashCode方法为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public   int  hashCode ( )   {
int  h   =  hash ;
if   (h   ==   0 )   {
int  off   =  offset ;
char  val [ ]   =  value ;
int  len   =  count ;

for   ( int  i   =   0 ;  i   &lt ;  len ;  i ++ )   {
h   =   31 *h   +  val [off ++ ] ;
}
hash   =  h ;
}
return  h ;
}

可以看得出,一个字符串的哈希值为s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1],是一个整数。也就是说所有的字符串可以通过hashCode()将其映射到整数的区间中,由于在java中整数的个数是有限的(四个字节有正负,第一位为符号位-2^31~2^31-1),当s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]足够大的时候可能会溢出,导致其变成负值。从上面的情况我们可以看出两个不同的字符串可能会被映射到同一个整数,发生冲突。因此java的开发人员选择了31这个乘数因子,尽量使得各个字符串映射的结果在整个java的整数域内均匀分布。

谈完java对象的哈希码,我们来看看今天的主角HashMap,HashMap可以看作是Java实现的哈希表。HashMap中存放的是key-value对,对应的类型为java.util.HashMap.Entry,所以在HashMap中数据都存放在一个Entry引用类型的数组table中。这里key是一个对象,为了把对象映射到table中的一个位置,我们可以通过求余法来,所以我们可以使用 [key的hashCode % table的长度]来计算位置(当然在实际操作的时候由于需要考虑table上的key的均匀分布可能需要对key的hashCode做一些处理)。

二.源码解析

相关属性

首先肯定是需要一个数组table,作为数据结构的骨干。

1
transient  Entry [ ]  table ;

这边定义了一个Entry数组的引用。
继续介绍几个概念把

  • capacity容量 是指数组table的长度
  • loadFactor 装载因子,是实际存放量/capacity容量 的一个比值,在代码中这个属性是描述了装载因子的最大值,默认大小为0.75
  • threshold(阈值)代表hashmap存放内容数量的一个临界点,当存放量大于这个值的时候,就需要将table进行夸张,也就是新建一个两倍大的数组,并将老的元素转移过去。threshold = (int)(capacity * loadFactor);

put方法详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
      public  V put (K key, V value )   {
          if   (key   ==   null )
              return  putForNullKey (value ) ;
          int  hash   =  hash (key. hashCode ( ) ) ;
          int  i   =  indexFor (hash, table. length ) ;
          for   (Entry <K,V >  e   =  table [i ] ;  e   !=   null ;  e   =  e. next )   {
              Object  k ;
              if   (e. hash   ==  hash   &&   ( (k   =  e. key )   ==  key   ||  key. equals (k ) ) )   {
                V oldValue   =  e. value ;
                e. value   =  value ;
                e. recordAccess ( this ) ;
                  return  oldValue ;
              }
          }

        modCount ++;
        addEntry (hash, key, value, i ) ;
          return   null ;
      }

在HashMap中我们的key可以为null,所以第一步就处理了key为null的情况。
当key为非null的时候,你也许会认为:恩,直接和table长度相除取模吧,但是这里没有,而是又好像做了一次哈希,这是为什么呢?这个还得先看indexFor(hash, table.length)方法,这个方法是决定存放位置的

1
2
3
      static   int  indexFor ( int  h,   int  length )   {
          return  h   &   (length - 1 ) ;
      }

明眼的都可以发现,因为在HashMap中table的长度为2^n(我们把运算都换成二进制进行考虑),所以h & (length-1)就等价于h%length,这也就是说,如果对原本的hashCode不做变换的话,其除去低length-1位后的部分不会对key在table中的位置产生任何影响,这样只要保持低length-1位不变,不管高位如何都会冲突,所以就想办法使得高位对其结果也产生影响,于是就对hashCode又做了一次哈希

1
2
3
4
5
6
7
    static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

当找到key所对应的位置的时候,对对应位置的Entry的链表进行遍历,如果以及存在key的话,就更新对应的value,并返回老的value。如果是新的key的话,就将其增加进去。modCount是用来记录hashmap结构变化的次数的,这个在hashmap的fail-fast机制中需要使用(当某一个线程获取了map的游标之后,另一个线程对map做了结构修改的操作,那么原先准备遍历的线程会抛出异常)。addEntry的方法如下

1
2
3
4
5
6
      void  addEntry ( int  hash, K key, V value,   int  bucketIndex )   {
    Entry <K,V >  e   =  table [bucketIndex ] ;
        table [bucketIndex ]   =   new  Entry <K,V > (hash, key, value, e ) ;
          if   (size ++   >=  threshold )
            resize ( 2   *  table. length ) ;
      }

get方法

1
2
3
4
5
6
7
8
9
10
11
12
13
      public  V get ( Object  key )   {
          if   (key   ==   null )
              return  getForNullKey ( ) ;
          int  hash   =  hash (key. hashCode ( ) ) ;
          for   (Entry <K,V >  e   =  table [indexFor (hash, table. length ) ] ;
             e   !=   null ;
             e   =  e. next )   {
              Object  k ;
              if   (e. hash   ==  hash   &&   ( (k   =  e. key )   ==  key   ||  key. equals (k ) ) )
                  return  e. value ;
          }
          return   null ;
      }

get方法其实就是将key以put时相同的方法算出在table的所在位置,然后对所在位置的链表进行遍历,找到hash值和key都相等的Entry并将value返回。

海思SD3403/SS928开发(二)环境搭建:从零到一构建高效开发环境的实战指南 本文详细介绍了海思SD3403/SS928开发板环境搭建的全流程,包括Ubuntu系统选择、基础配置、开发环境依赖安装、SDK工具链配置等关键步骤。通过实战经验分享,帮助开发者避免常见问题,快速构建高效开发环境,提升开发效率。 阅读详情

相关推荐

适合初学者的ROS机器人教程(1): Ubuntu下ROS创建自己的包和使用github下载的包

ROS如何创建自己的包 1. 首先创建catkin工作空间(如果你以及有了请忽略) $ mkdir catkin_ws # 创建一个文件夹里面计划放我们的项目,ws是workspace的缩写 $ cd catkin_ws # 将当前工作文件夹切换到catkin_ws这个文件夹 ~/catkin_ws$ catkin_make # 执行catkin_make创建一ROS项目 此时你的catkin_...

@司南牧|知乎|博客|易懂教程|李韬 3244

HashMap源码分析与实现

面试的时候经常会遇见诸如:“java中的HashMap是怎么工作的”,“HashMap的get和put内部的工作原理”这样的问题。本文将用一个简单的例子来解释下HashMap内部的工作原理。每当hashmap扩容的时候需要重新去add Entry对象,需要重新hash,然后放入我们新的entry table数组里面。如果在工作中,已经知道hashmap需要存多少值,几千或者几万的时候,最好新指定题...

朱培(知浅_ZLH) 6230

IEEE论文接收后,收到Proof邮件别慌!手把手教你48小时内搞定校样(附详细截图)

本文详细解析了IEEE论文接收后Proof校样的全流程,从邮件关键信息解码到精准修改标注,提供48小时内高效完成校样的实战指南。涵盖登录技巧、文件验证、修改标注黄金法则及九宫格检查清单,帮助作者避免常见错误,确保论文顺利发表。

weixin_30652491的博客 563

HashMap的底层原理分析

每次添加元素时,HashMap会根据数组当前容量和加载因子计算扩容阈值,当HashMap中的key-value键值对的数量超出扩容阈值时,数组会自动按照2倍进行扩容,扩容后数组的最大容量不会超过1073741824(2^30)当添加一个新的key-value键值对时,会根据key的hashCode(),通过哈希函数计算出一个新的hash哈希值,并通过这个hash哈希值,计算key-value键值对在数组中的下标位置(桶 Bucket)

2301_78423465的博客 1437

深入解析FDTD Script中的farfieldpolar3d命令:复电场计算与应用

本文深入解析了FDTD仿真中`farfieldpolar3d`命令的核心功能与应用。作为`farfield3d`的复数版本,该命令能计算并返回球坐标系下的复电场矢量,完整保留振幅与相位信息,为光学天线设计、近远场变换及偏振态分析等高级应用提供了关键数据基础。

grafana6viz的博客 286

手把手教你学 NPU 驱动开发--第一部分:基础准备 开发环境搭建:Linux 内核编译与 NPU 仿真平台配置

摘要:本文详细介绍了搭建Linux内核编译与NPU仿真平台的完整流程。主要内容包括:1) 准备ARM64交叉编译工具链;2) 编译支持KGDB调试的Linux内核;3) 构建最小根文件系统;4) 使用QEMU模拟自定义NPU设备;5) 配置KGDB调试环境。文章重点讲解了如何通过修改设备树添加虚拟NPU设备,并提供了开源NPUIP Gemmini作为实验对象。最后展示了简单的"Hello NPU"驱动验证方法,为后续NPU驱动开发奠定了基础。文中还附有常用命令速查表,方便开发者快速搭建调

MHD0815的博客 382

HashMap原理分析及性能优化

文章目录一.HashMap是什么二.HashMap继承类对比分析三.HashMap源码相关单词含义四.HashMap如何确定哈希桶数组索引位置五. HashMap 的 put 方法分析六.HashMap扩容机制七.HashMap线程安全性 一.HashMap是什么 HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。 HashMap是一个用于存储Key-Value键值...

末尾卡片和博主交流学习! 5741

js逆向第3例:蝉妈妈大屏实时数据formDataSign加密

蝉妈妈大屏抖音直播实时数据是抓取抖音直播间数据的实时数据,有类似需求的公司但没有能力去搞定抖音,抓取蝉妈妈数据也是一个不错的选择。接下来就是调试找到加密点,说句题外话蝉妈妈的所有js文件打包方式用的都是webpack5,那如何区分打包方式可以阅读这篇文章。时间戳 ,经过观察发现加密参数是实时变化的。这个函数扣下来,在浏览器控制台中测试一下。言归正传,打断点找到变量。继续打断点向上找又发现了。查看请求参数,可以看到。请求的内容,那么这个。

花臂不花Home 1539

HashMap原理分析

HashMap 1.HashMap简介 HashMap实现Map接口,以键值对的形式存储数据, HashMap<Integer,String> hashmap = new HashMap<>(); hashmap.put(1,"zhangsan"); hashmap.put(2,"lisi"); hashmap.put(3,"wangwu"); hashmap.put(4,...

SK_Jaco的博客 988

基于YOLOv8的植物生长阶段识别系统设计与实现

本文详细介绍了一个基于YOLOv8深度学习模型的植物生长阶段识别系统,包含完整的数据集构建、模型训练、评估以及基于PyQt5的用户界面开发过程。系统能够自动识别植物在不同生长阶段(如发芽期、生长期、开花期、结果期等)的形态特征,为精准农业和智能种植提供技术支持。文章提供了完整的代码实现,并讨论了模型优化策略和实际应用场景。关键词:植物生长阶段识别、YOLOv8、目标检测、PyQt5、深度学习。

YOLO 465

流体仿真软件:COMSOL Multiphysics二次开发_(3).流体仿真基础理论

流体仿真基础理论包括流体的基本性质、流体运动方程、边界条件、初始条件、湍流模型和网格生成等。在使用COMSOL Multiphysics进行流体仿真时,正确设置这些参数和条件是获得准确仿真结果的关键。通过实际应用案例,可以更好地理解如何在COMSOL中进行流体仿真,并提取和分析仿真结果。希望本文对流体仿真初学者和从业人员有所帮助。

kkchenjj的博客 1341

HashMap实现原理及源码分析

HashMap实现原理及源码分析

u010919133的专栏 212

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

HashMap的实现原理及部分源码分析

HashMap的实现原理及部分源码分析 ​  Java8拥有增强的Map集合,Map是用来保存具有映射关系的数据。Map接口提供了大量的实现类,典型的实现有 HashMap和Hashtable等。Hashtable从它的类名看起来就很古老,因为它的命名都没有遵守Java的命名规范:每个单词首字母都应该大写。 HashMap(哈希表又叫散列表),在数据结构我们已经接触过,它的应用非常广泛,...

weixin_42378758的博客 268

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

HashMap死锁原因分析及原理

一、HashMap原理 1.HashMap的本质就是数组和链表。table是一个entry数组,每一个数组元素保存一个Entry节点,而Entry节点内部又连接着同样key的下一个Entry节点,就构成了链表。. 详情见 HashMap源码分析 2.HashMap死锁原因: HashMap会造成死锁,因为HashMap是线程非安全的,多并发的情况容易造成死锁,若要高并发推荐使用Concurren...

rlk512974883的博客 9178

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

HashMap底层原理分析

HashMap底层原理分析 算法复杂度 算法复杂度分为时间复杂度和空间复杂度. 其作用: 时间复杂度是度量算法执行的时间长度; 而空间复杂度是度量算法所需存储空间的大小. 时间复杂度 1. 时间频度 一个算法执行所耗费的时间, 从理论上是不能算出来的, 必须上机运行测试才能知道, 但我们不可能也没有必要对每个算法都上机测试, 只需知道哪个算法花费的时间多, 哪个算法花费的时间少就可以了. 并且一个算法的时间与算法中语句的执行次数成正比例, 哪个算法中语句执行次数多, 它花费时间就多, 一个算法的语句执行

西门飘雪,东门飞︿( ̄︶ ̄)︿的博客 210

hashmap怎么取值_hashmap原理分析

本文准备从以下几个方面去讲解HashMap:1)HashMap源码详细分析2)HashMap为什么是线程不安全的?3)HashMap和HashTable的区别4)1.7和1.8的HashMap实现区别总结HashMap源码分析一、构造函数让我们先从构造函数说起,HashMap有四个构造方法,别慌1.1 HashMap() // 1.无参构造方法、 // 构造一个空的HashMap,初始容量为...

weixin_39566593的博客 939

HashMap的原理分析

这是一个考察Java基础知识的题目,面试常用,这里专门整理一下答案和原理分析。 这次我比较过粪,不是由浅入深,而是直接给结论,因为考虑到你打开这个文章,只是来抓紧查一下总结,时间相当紧迫。^^ 1. 结论 说结论,改成说HashMap的特点,也不是不可以^^ 存储用数组,数组长度为2个倍数,且在数据个数超过0.75倍时,动态扩大 存储/查找 根据hash值找到数组下标,时间复杂度基本为O(1) 线程不安全 2 细化分析 2.1 存储用数组,数组长度为2个倍数,且在数据个数超过0.75倍时,动态扩大 数组的类

newchenxf的专栏 310

hashmap原理分析

一、背景 在java中,HashMap是很常用的一种数据结构,最近重新温习了一下,这里以源码层面来分析总结一下HashMap,如有不合理或疑问的地方,欢迎沟通交流。 HashMap是Java中的一个容器,继承自AbstractMap抽象类,实现Map接口,简单画了一张Map家族的类图(只画了部分常用的接口和类): 在Map家族中的众多实现类里面,我们做最常用的操作就是实例化Map、map.put(...

u014368614的博客 154

【HashMap原理分析】

HashMap是基于哈希表实现的Map接口类,具有快速访问特性但不保证顺序。其核心属性包括默认初始容量16、负载因子0.75、树化阈值8等。采用数组+链表+红黑树的存储结构,当链表长度超过8且容量大于64时转为红黑树。通过hash(key)计算索引位置,使用高位异或运算减少哈希冲突。HashMap非线程安全,允许多线程写入时可能导致数据不一致。其工作原理是通过哈希函数定位桶位置,采用链表或红黑树处理冲突,动态扩容机制确保性能。

weixin_44078411的博客 827

HashMap实现原理分析

HashMap可以说是Java中最常用的集合类框架之一,是Java语言中非常典型的数据结构,我们总会在不经意间用到它,很大程度上方便了我们日常开发。我们使用他的时候往往只是只会用,不知道内部怎么实现的,在这里我复习之余,简单总结了下。

weiythi的Blog 317

HashMap实现原理以及性能分析

本篇博文转自:DannyHoo的HashMap实现原理及源码分析 一、背景 在java中,HashMap是很常用的一种数据结构,最近重新温习了一下,这里以源码层面来分析总结一下HashMap,如有不合理或疑问的地方,欢迎沟通交流。 HashMap是Java中的一个容器,继承自AbstractMap抽象类,实现Map接口,简单画了一张Map家族的类图(只画了部分常用的接口和类): 在Map家族中...

iWuxingyun的博客 1219

HashMap原理分析,大厂面试题

HashMap的工作原理是什么? HashMap内部是通过一个数组实现的,只是这个数组比较特殊,数组里存储的元素是一个Entry实体(在Java8中为Node),这个Entry实体主要包含Key、value以及一个指向自身的next指针。 static class Node<K,V> implements Map.Entry<K,V> { final int hash; ...

后面牵个人的博客 1526
上一篇: 砝码称重
下一篇: 哈夫曼编码
easyjsf
博客等级 码龄17年 19粉丝 82原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值