一、前言

看到标题大家都应该觉得奇怪,我们去面试被问到HashMap的实现,大家不都是说的基于数组+链表的方式么。为什么我们会说HashMap不是基于数组+链表的方式实现的呢?其实这是大家的狭义理解导致的。真正的HashMap是广义的概念,我们平常所说的HashMap都是只Java里面的HashMap实现。这只是所有HashMap实现方法中的一种。

广义的HashMap从寻址方式上分为Open Addressing HashMap和Closed Addressing HashMap。而Open Addressing又根据探测技术细分为:Linear Probing、Quadratic Probing和Double Hashing等。在Open Addressing中又有Primary Clustering和Secondary Clustering的概念。

看到这些概念想必很多同学都已经晕了,是不是发现很多概念都没有听过。接下来楼主就为大家详细讲解广义HashMap的概念和底层实现原理。

二、广义HashMap的原理

1、寻址方式

首先我们简单介绍下什么是寻址方式。对Java HashMap有了解的同学对寻址方式应该不陌生。在Java HashMap中,其主要结构是数组+链表。我们根据Hash查找一个key应该落在数组中的哪个位置的过程就叫做寻址。寻址即如何找到Key在数组中的位置。因为所有的HashMap能够快速找到数据基础都是数组的快速定位。

广义的HashMap基于寻址方式的不同,将HashMap分为两大类:Open Addressing(开放寻址)和Closed Addressing(闭合寻址)。下面我们将对其进行深入讲解。

A、Closed Addressing(闭合寻址)

这种寻址方式大家容易理解。因为Java HashMap中的寻址方式就是Closed Addressing。其特点为:无论什么元素(元素的key)只要其通过hash定位到数组的某个位置,那么它必须放在这个位置。当出现Hash冲突的时候,我们可以使用额外存储空间来解决,如Java HashMap中使用链表的方式。

B、Open Addressing(开放寻址)

开发寻址方式是相对闭合寻址方式而言。即当我们通过hash的方式将元素(元素的key)定位到数组的某个位置时,我们不必一定将该元素放在数组的这个位置。而是可以通过其他的寻址方式(后续会讲解)将其放到数组的其他位置。

通过下图我们就可以明显看出两种寻址方式的不同(其中数字0-9表示数组的位置,而圆形圈中的数组则表示对应的元素)。

C、Open Addressing vs Closed Addressing

通过上面的分析我们可以看到Open Addressing和Closed Addressing有比较明显的区别。下面做了基本的总结:

对比项 Open Addressing Closed Addressing 说明
元素容量 最大为数组大小 最大会比数组大小大很多 不考虑扩容的场景
数据聚集 存在数据聚集问题 不存在数据聚集问题 说明是数据聚集参考后面的章节
缓存行利用 可以充分利用缓存行 不能利用缓存行 利用缓存行可以提高读取性能
空间占用 仅为数组大小 会占用比数组大小更多的空间 不考虑元素自身大小
高load factor性能 load factor=0.9时性能都极好 load factor=0.75就需要扩容 load factor:数组元素相对数组大小的占比

说明:

  • 因为Closed Addressing会通过链表等额外空间的方式存储元素,所以它的容量比较大。可以说上限
  • 数据聚集(后续会介绍)会导致读取数据慢
  • Open Addressing的数据都放在数组中,数组结构可以充分利用CPU能力被加载到同一个缓存行中,从而提高读取性能
  • Closed Adressing有链表等额外结构空间,会导致占用更多的内存。
  • Closed Addressing使用链表解决Hash冲突,导致高load factor下链表很长,且不能利用缓存行导致查询性能很差。而Open Addressing的数据都在一个数组中,且能够利用缓存行,同时好的Open Addressing实现能够很好的平衡数据聚集问题,所以其在load factor=0.9时都能够有很好的性能。

通过上面的对比我们可以看到Open Addressing还是有很多优势的。特别是在高load factor下的性能表现,再加上大家都Closed Addressing的代表实现Java HashMap应该是非常的熟悉了,且网上也有很多文章讲解Java HashMap,因此本文就不再对Closed Addressing做进一步讲解。接下来我们重点讲解大家比较陌生的Open Addressing HashMap。

2、Open Addressing的探测技术

因为Open Addressing没有额外空间来解决hash冲突的问题,因此当存在Hash冲突的时候,其需要在数组中为这个元素找到一个空位置。这个当遇到hash冲突去寻找空位置的过程就叫做探测。这里使用的技术就叫做寻址技术。目前比较常用的寻址技术有Linear Probing、Quadratic Probing和Double Hashing。接下来我们详解对齐进行介绍。

A、Linear Probing(线性探测)

线性探测的方式比较简单。当写入元素的时候出现hash冲突时,我们直接去查看该位置的下一个是否可用,如果可用则直接插入元素。否则继续查找该位置的下一个,一直这样循环处理,知道找到合适的位置,或者触发Rehash。

这种探测技术计算可用位置的公式如下(i为通过hash确定的初始位置):

  • i + 1
  • i + 2
  • i + 3

这种探测方式存在一个问题。它会导致大量的元素聚集在一块,形成一个连续的链(物理地址上连续,不是链表)。当我们查找的数据在这个链里面的时候,需要不断一个一个查找。如果链越长,则查询的效率则越低。这种数据聚集在一起的现象就叫做聚集(Clustering),也可以叫做Primary Clustering。

B、Quadratic Probing(二次方探测)

二次方探测也比较简单,就是每次计算可用位置的时候不是直接+1,而是加二次方。

这种探测技术计算可用位置的公式如下(i为通过hash确定的初始位置):

  • i + 1*1
  • i + 2*2
  • i + 3*3

这种方式通过这种次方跳跃的方式寻找可用位置,虽然不容易产生Primary Clustering。但是也会产生另外一种链,比如hash冲突很严重(大量元素的hash到同一个位置),那么这些元素也会构成一个链(物理上不连续),在查找的时候仍然会导致查询慢的问题。这种数据链也是一种数据聚集。而这种数据聚集就叫做Secondary Clustering。

C、Double Hashing(二次Hash探测)

二次Hash探测顾名思义,就是当出现hash冲突的时候通过另外一个hash来计算下一个可用位置。

这种探测技术计算可用位置的公式如下(i为通过hash确定的初始位置,j=另外一个hash(key)值):

  • i + 1 × j
  • i + 2 × j
  • i + 3 × j

这种探测技术,相对来说就不会出现Primary Clustering和Secondary Clustering了。具体原因大家可以思考一下。

D、Linear Probing vs Quadratic Probing vs Double Hashing

探测技术

冲突slot计算方式

优点

缺点

Linear Probing

  • i + 1
  • i + 2
  • i + 3

很好的利用缓存行

存在primary clustering问题

存在secondary clustering问题

Quadratic Probing

  • i + 1*1
  • i + 2*2
  • i + 3*3

有间隙可以避免primary clustering

存在secondary clustering问题

无法利用缓存行

Double Hashing

j=hash2(key)

  • i + 1 × j
  • i + 2 × j
  • i + 3 × j

避免了primary clustering问题

避免了secondary clustering问题

无法利用缓存行

性能差(多一次hash)

3、Clustering元素聚集

元素聚集就是只元素在数组上形成一个链,可以是物理上连续的,也可以是物理上不连续的。这种链会导致数查询的时候性能降低。就像Java HashMap中一样,当链表变长了之后HashMap的查询效率就会降低。

A、Primary Clustering

就是说元素聚集发生在物理上的连续,即在数据上的元素相邻挨着一起,中间无间隔。

B、Secondary Clustering

表示数组上的元素根据探测技术的算法形成了一个物理上不连续,但是在探测算法上连续的链。即通过通过探测技术的探测可用空位时,发现多次计算的位置都是被占用的,这就形成了一个物理上不连续但是逻辑上连续的链。

三、Open Addressing的增删查

Open Addressing的增删查和咱们熟知的Closed Addressing还是有很多不同。接下来我们就以线性探测(Linear Probing)为基础对Open Addressing Hash Map的增删查分别多进一步的讲解。

1、删除元素

在Open Addressing中要删除元素主要涉及到三种场景,如下图:

场景1:元素独立存在(图中元素12)

由于该元素独立存在于数组中,我们只需要直接将其从数组中删除接口。

场景2:元素在Clustering末尾(图中元素9)

这种场景和场景1是一样的,我们可以直接将其从数组中删除即可。

场景3:元素在Clustering中(图中元素6)

这种场景和前面的场景则不一样了。如果我们直接将元素6从数组中删除。则会导致一个问题,即下次查询的时候会查询不到7和8。因为由于6被删除,查询的时候查询到空位置则终止了(参考后续查询逻辑)。

这个问题的解决办法有如下两种:

tombstones法:即被删除的元素不直接从数组中删除,而是标记为不可用,查询的时候仍然可以继续向后查询到元素7和8。这样被标记删除的元素叫做tombstones,即墓碑。

backward shift deletion法:元素6被删除之后,该链中后面的元素都向前移动一格。这样元素4、5、7、8就挨着一起了。也不会影响后续的查询。

这两种删除方式各有各优缺点。

删除方式 执行速度 查询性能影响 性能测试
tombstones 直接标记,速度快 删除元素不删除,会导致链很长,影响查询性能 罗宾汉墓碑法
backward shift deletion 需要移动元素,速度慢 删除元素被删除,不会影响查询性能 罗宾汉backward shift法

PS:墓碑法的性能比较低,直接使用backward shift deletion法,其性能很高。截图可以参考中连接关于罗宾汉HashMap的两种不同删除元素的性能测试连接。

2、查找元素

查找元素就比较简单了,首先根据hash直接定位到数组的位置,然后对key进行equal比较。如果key不匹配则通过对应的探测技术继续探测下一个位置。如果在探测的过程中发现了墓碑(tombstones)元素,则跳过,即寻找下一个。一直循环要么找到对应的元素,要么找到一个空位置结束。

3、插入元素

首先根据hash定位到数组的位置,如果该位置为空或者为墓碑(tombstones),则将元素放在该位置,结束插入操作。如果不为空或者墓碑,则通过对应的探测技术继续寻找下一个位置,如此循环直到找到空位置或者墓碑位置,则插入元素。或则触发rehash,重新查找。

关于广义HashMap的技术与原理就介绍到这里,下期我们将介绍Open Addressing HashMap的具体实现例子:ThreadLocal和Robin Hood HashMap。

四、惯例

如果你对本文有任何疑问或者高见,欢迎添加公众号lifeofcoder共同交流探讨(添加公众号可以获得楼主最新博文推送以及”Java高级架构“上10G视频和图文资料哦)。

HashMap原理详解:探测技术(Probing)、数据聚集(Clustering)、寻址方式(Addressing)、墓碑删除(tombstones)等技术的深度剖析相关推荐

  1. 【Java基础】HashMap原理详解

    [Java基础]HashMap原理详解 HashMap的实现 1. 数组 2.线性链表 3.红黑树 3.1概述 3.2性质 4.HashMap扩容死锁 5. BATJ一线大厂技术栈 HashMap的实 ...

  2. HashMap原理详解(基于jdk1.8)

    HashMap原理详解(基于jdk1.8) HashMap原理详解,有兴趣的同学可以看下.有错误的地方也希望大佬们能指点下. HashMap的内部存储是一个数组(bucket),数组的元素Node实现 ...

  3. HashMap 原理详解

    一.HashMap的原理详解 首先我们要知道什么是哈希表以及它的结构.在介绍哈希表之前我们需要了解并且掌握数组.链表以及红黑树的结构以及特点. 1.我们先来看一下HashMap的使用 public c ...

  4. Java集合篇:HashMap原理详解(JDK1.7及之前的版本)

    (本文有关HashMap的源码都是基于JDK1.6的) 摘要: HashMap是Map族中最为常用的一种,也是 Java Collection Framework 的重要成员.本文首先给出了 Hash ...

  5. Java集合篇:HashMap原理详解(JDK1.8)

    概述 JDK 1.8对HashMap进行了比较大的优化,底层实现由之前的"数组+链表"改为"数组+链表+红黑树",本文就HashMap的几个常用的重要方法和JD ...

  6. HashMap原理详解

    一.hashmap简介 hashmap是Java当中一种数据结构,是一个用于存储Key-Value键值对的集合,每一个键值对也叫作Entry. 二.JDK7的HashMap 1.JDK7时HashMa ...

  7. java源码系列:HashMap底层存储原理详解——4、技术本质-原理过程-算法-取模具体解决什么问题

    目录 简介 取模具体解决什么问题? 通过数组特性,推导ascii码计算出来的下标值,创建数组非常占用空间 取模,可保证下标,在HashMap默认创建下标之内 简介 上一篇文章,我们讲到 哈希算法.哈希 ...

  8. 大数据是什么和大数据技术十大核心原理详解

     一.数据核心原理   从"流程"核心转变为"数据"核心   大数据时代,计算模式也发生了转变,从"流程"核心转变为"数据&quo ...

  9. 大数据技术十大核心原理详解

    一.数据核心原理--从"流程"核心转变为"数据"核心 大数据时代,计算模式也发生了转变,从"流程"核心转变为"数据"核心 ...

  10. Java HashMap的实现原理详解

    HashMap是Java Map类型的集合类中最常使用的,本文基于Java1.8,对于HashMap的实现原理做一下详细讲解. (Java1.8源码:http://docs.oracle.com/ja ...

最新文章

  1. 模型树——就是回归树的分段常数预测修改为线性回归 对于非线性回归有较好的预测效果...
  2. URI URL 简介区别
  3. oracle 回收undo,oracle 释放undo空间
  4. Codeforces Round #542 [Alex Lopashev Thanks-Round] (Div. 1)
  5. excel数据透视表列名更改
  6. 敏捷开发框架_他山之石-敏捷开发管理框架在设计项目中的应用
  7. mysql一些操作个人备忘(持续更新)
  8. 有着 30 多年经验的程序员最终被辞退了
  9. Spark 解析 : DAGScheduler中的DAG划分与提交
  10. 使用Automake,Autoconf生成Makefile
  11. (持续更新)C语言笔试题整理
  12. 笔记本电脑修改BIOS及刷写教程
  13. DRILLNET 2.0------第十三章 尾管固井扭矩/摩阻模型
  14. 算法训练Day24 | 回溯算法理论基础;LeetCode77.组合(经典的回溯问题)
  15. 计算机各键的名称和作用,space是什么键 键盘键位名称及功用详解
  16. lower_bound()/upper_bound()函数(C++)
  17. 期货开户需要具备⼀定的条件
  18. H.264 高度压缩数字视频编解码器标准
  19. 应用技术大公开系列Q之十一:(纤维).石墨烯纸制备工艺 (*3-4)
  20. openwrt旁路由的设置

热门文章

  1. thinkpad电源管理解决办法 win2019
  2. 吉司机线段树(segment tree beats!)
  3. 黑马程序员-----视频看完了,谈谈自己的感受
  4. java match详解_Match类解析
  5. 怎么自学编程python_怎样自学Python编程?
  6. 数据告诉你,谁是2019年最硬核公链?
  7. Hadoop的容错性
  8. 记一次zip压缩包打开异常问题
  9. 图书整理系统(1)-创建图书分类文件夹
  10. 微信小程序数据库更新数据说明