浅谈HashMap 的底层原理

本文整理自漫画:什么是HashMap? -小灰的文章 。已获得作者授权。



HashMap 是一个用于存储Key-Value 键值对的集合,每一个键值对也叫做Entry。这些个Entry 分散存储在一个数组当中,这个数组就是HashMap 的主干。

HashMap 数组每一个元素的初始值都是Null

1. Put 方法的原理

调用Put方法的时候发生了什么呢?

比如调用 hashMap.put("apple", 0) ,插入一个Key为“apple"的元素。这时候我们需要利用一个哈希函数来确定Entry的插入位置(index):

index = Hash("apple")

假定最后计算出的index是2,那么结果如下:

但是,因为HashMap的长度是有限的,当插入的Entry越来越多时,再完美的Hash函数也难免会出现index冲突的情况。比如下面这样:

这时候该怎么办呢?我们可以利用链表来解决。

HashMap数组的每一个元素不止是一个Entry对象,也是一个链表的头节点。每一个Entry对象通过Next指针指向它的下一个Entry节点。当新来的Entry映射到冲突的数组位置时,只需要插入到对应的链表即可:

新来的Entry节点插入链表时,使用的是“头插法。

2. Get方法的原理

使用Get方法根据Key来查找Value的时候,发生了什么呢?

首先会把输入的Key做一次Hash映射,得到对应的index:

index = Hash(“apple”)

由于刚才所说的Hash冲突,同一个位置有可能匹配到多个Entry,这时候就需要顺着对应链表的头节点,一个一个向下来查找。假设我们要查找的Key是“apple”:

第一步,我们查看的是头节点Entry6,Entry6的Key是banana,显然不是我们要找的结果。

第二步,我们查看的是Next节点Entry1,Entry1的Key是apple,正是我们要找的结果。

之所以把Entry6放在头节点,是因为HashMap的发明者认为,后插入的Entry被查找的可能性更大。

3. HashMap的初始长度

初始长度为16,且每次自动扩容或者手动初始化的时候必须是2的幂。

如何进行位运算呢?有如下的公式(Length是HashMap的长度):

之前说过,从Key映射到HashMap数组的对应位置,会用到一个Hash函数:

index = Hash(“apple”)

如何实现一个尽量均匀分布的Hash函数呢?我们通过利用Key的HashCode值来做某种运算。

index = HashCode(Key) & (Length - 1)

下面我们以值为“book”的Key来演示整个过程:

  1. 计算book的hashcode,结果为十进制的3029737,二进制的101110001110101110 1001
  2. 假定HashMap长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111。
  3. 把以上两个结果做与运算,101110001110101110 1001 & 1111 = 1001,十进制是9,所以 index=9。

    可以说,Hash算法最终得到的index结果,完全取决于Key的Hashcode值的最后几位。这里的位运算其实是一种快速取模算法。

HashMap 的size为什么必须是2的幂?。这是因为2的幂用二进制表示时所有位都为1,例如16-1=15 的二进制就是1111B。我们说了Hash算法是为了让hash 的分布变得均匀。其实我们可以把1111看成四个通道,表示跟1111 做&运算后分布是均匀的。假如默认长度取10,二进制表示为1010,这样就相当于有两个通道是关闭的,所以计算出来的索引重复的几率比较大。

想看原作者的文章可以看看这个公众号。

时间: 2024-11-08 11:42:07

浅谈HashMap 的底层原理的相关文章

浅谈 HashMap 实现原理

1.HashMap概述 HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 2.HashMap的数据结构 在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外.HashMap实际上是一个"链表散列"的数据结构,即数组和链表的结合体. 从上图中可以看出,HashMap底层就

HashMap的底层原理

简单说: 底层原理就是采用数组加链表: 两张图片很清晰地表明存储结构: 既然是线性数组,为什么能随机存取?这里HashMap用了一个小算法,大致是这样实现: // 存储时: int hash = key.hashCode(); // 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值 int index = hash % Entry[].length; Entry[index] = value; // 取值时: int hash = key.hashCode()

浅谈HashMap

Java集合类的整体架构 比较重要的集合类图如下:   有序否 允许元素重复否 Collection 否 是 List 是 是 Set AbstractSet 否 否 HashSet TreeSet 是(用二叉树排序) Map AbstractMap 否 使用 key-value 来映射和存储数据, Key 必须惟一, value 可以重复 HashMap TreeMap 是(用二叉树排序) HashMap详解 HashMap 和 HashSet 是 Java Collection Framew

浅谈HashMap实现原理

我们都知道数组和链表的优劣,那HashMap有效地整合了数组和链表,形成了新的一种数据装载模型. 利用数组+链表的优势 1.减少数组不必要空间的开辟(假如单纯利用数组实现装载,我们总要考虑预先分配一部分内存,总不能需要的时候才开辟吧?在设计理念上也不符),利用链表能够更好地实现动态分配内存(通过引用关系指定): 2.通过散列均匀地将下标一致的对象挂在相应的链表下,这样的好处是通过数组和链表的整合减少了查询的复杂度(遍历最大次数为 数组长度+某链表下长度) 3.总之一句话"悟天克斯的合体"

TODO:浅谈pm2基本工作原理

要谈Node.js pm2的工作原理,需要先来了解撒旦(Satan)和上帝(God)的关系. 撒旦(Satan),主要指<圣经>中的堕天使(也称堕天使撒旦),他是反叛上帝耶和华的堕天使(Fallen Angels),曾经是上帝座前的天使,后来他因骄傲自大妄想与神同等而堕落成为魔鬼,被看作与上帝的力量相对的邪恶.黑暗之源. 简单的说Satan是破坏神,就是进程的异常退出.kill等:God是守护神,保护进程.重启进程等. 一图胜千言,pm2的 RPC基本框架.Client与Daemon是采用了R

[转自SA]浅谈nginx的工作原理和使用

nginx apache 简单对比 nginx 相对 apache 的优点: 轻量级,同样起web 服务,比apache 占用更少的内存及资源 抗并发,nginx 处理请求是异步非阻塞的,而 apache 则是阻塞型的,在高并发下 nginx 能保持低资源低消耗高性能 高度模块化的设计,编写模块相对简单 社区活跃 配置简洁 apache 相对nginx 的优点: rewrite ,比 nginx 的 rewrite 强大 模块超多 少 bug ,nginx 的 bug 相对较多 超稳定 配置复杂

JAVA NIO之浅谈内存映射文件原理与DirectMemory

Java类库中的NIO包相对于IO 包来说有一个新功能是内存映射文件,日常编程中并不是经常用到,但是在处理大文件时是比较理想的提高效率的手段.本文我主要想结合操作系统中(OS)相关方面的知识介绍一下原理. 在传统的文件IO操作中,我们都是调用操作系统提供的底层标准IO系统调用函数  read().write() ,此时调用此函数的进程(在JAVA中即java进程)由当前的用户态切换到内核态,然后OS的内核代码负责将相应的文件数据读取到内核的IO缓冲区,然后再把数据从内核IO缓冲区拷贝到进程的私有

浅谈GIT之底层对象理解

一.基本概述 首先定下一个基调就是Git并不是比SVN任何方面都好.SVN在某些功能或方面或者某些场景下也优于Git. 只是这篇文章讨论涉及到的点要与SVN相比,然后突出Git的某些方面的优越性而已. 对于Git而言,它本身是一个内容寻址文件系统,会将需要存储的东西按元数据meta data方式存储在一个KV数据库里面, 类似HashMap的这样的键值对结构,然后会按照内容生成相应的唯一hashcode作为内容的key, 而对于SVN而言,则是按照文件方式存储. 所以,看出一个特点就是如果需要的

HashMap 的底层原理

1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端. 数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大.但数组的二分查找时间复杂度小,为O(1):数组的特点是:寻址容易,插入和删除困难: 链表 链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N).链表的特点是:寻址困难,插入和删除容易. 哈希表 那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表.