哈希表的扩容实现机制

  • 导语
  • 哈希表
    • 什么是哈希表
    • 装载因子
    • Hashcode
    • 哈希冲突
  • 扩容方案
    • Java中的实现
    • Redis中的实现
  • 结束语

导语

哈希表是实际开发中非常常用的数据结构,也很重要。了解其底层实现细节也是非常重要的。
我这篇主要是想记录一下自己的学习结果,是关于不同应用情形下实现哈希表扩容的不同方案。

哈希表

什么是哈希表

哈希表是一个散列表,里面存储的是键值对(key-value)映射。它是一种根据关键码key来寻找值value的数据映射结构。

装载因子

装载因子,也叫负载因子(load factor),它表示散列表的装满程度。

装载因子α=元素个数/散列表长度。

当当前表的实际装载因子达到默认的负载因子值(负载极限)时,就会触发哈希表的扩容。

一般情况下,默认的负载因子值不能太大,因为其虽然减少了空间开销,但是增加了查询的时间成本;也不能太小,因为这样还会增加rehash的次数,性能较低。

Hashcode

哈希码是一种算法,尽量为不同的对象生成不同的哈希码。(但不代表不同对象的哈希码一定不同。)它可以作为相同对象判断的依据。同一对象如果没有经过修改,前后不同时刻生成的哈希码应该是一致的。

不过我们知道,判断是否相同已经有了equals()方法,那为什么还需要Hashcode()方法呢
这是因为equals()方法的效率远不如Hashcode()方法。

同样的问题,既然Hashcode()性能那么高,那为什么还需要equals()方法呢
这是因为equals()方法是完全可靠的,而仅仅基于哈希码比较是不完全可靠的。

也就是说:

如果两个对象相同,hashcode一定相同。
但是hashcode相同的两个对象不一定相同。
而如果两个对象相同,equals()方法得到的一定为true。

所以说Java中的HashMap既提供对equals()的重写,也提供对Hashcode()的重写。
于是,对于这种有着大量且快速的对象对比需求的hash容器,我们将两种方法结合起来用。

先使用Hashcode()方法,如果两个对象产生的哈希码不相同,那么这两个对象一定不同,不再进行后续比较;
而如果两个对象产生的哈希码相同,那么这两个对象有可能相同,于是再使用equals()方法进行比较。

哈希冲突

哈希冲突是指,不同的key经由哈希函数,映射到了相同的位置上,造成的冲突。

哈希冲突是不可避免的,但是如果冲突较严重就会影响哈希表的性能。

我们一般采用四种方式来解决哈希冲突:开放定址法、链地址法、再哈希法、建立公共溢出区。我在之前的一篇博文里有过详细介绍和举例说明。解决哈希冲突的几种办法(举例推演)。

扩容方案

当当前哈希表的装载因子过大,哈希冲突一般较严重,其增删改查的性能也随之降低。
这个时候,我们会采取扩容机制,增大哈希表的容量。

Java中的实现

我们以HashMap为例,来探究一下Java中关于哈希表扩容的实现。

首先是,每次扩容时,哈希表的容量增加为原先的两倍
于是在扩容被触发时(实际装载因子达到默认装载因子时),需要对原先的表进行rehash。所以这时增加一个元素的性能是比较差的,因为要等待原先的表rehash之后才能增加该元素。

其冲突解决的方式是链地址法

当某个箱子的链表长度大于8时,Java的处理方案是将其转化为红黑树。当链表长度小于6时,从红黑树转换为链表。

这是因为初始默认的箱子个数(哈希表容量)为16,而根据泊松分布,当负载因子达到0.75时,某个箱子的链表长度为8的概率为0.00000006,这种可能性是小到可以忽略的。我们甚至可以断言,如果有了这种情况的出现,那一定是哈希函数设计的不合理所导致的。

而红黑树的插入删除、查询的性能都比较良好。所以Java的这种转化机制一定程度上避免了不恰当的哈希函数导致的性能问题。

Redis中的实现

Redis 是一个高效的 key-value 缓存系统,也可以理解为基于键值对的数据库。

Redis也是采取链地址法解决哈希冲突。

我们知道,Java发生扩容的瞬间,是需要先将原哈希表中所有键值对都转移到新的哈希表中,这个过程是比较慢的,此时插入该元素的性能相当低。

而Redis对于这一部分,采取的是分摊转移的方式。即当插入一个新元素x触发了扩容时,先转移第一个不为空的桶到新的哈希表,然后将该元素插入。而下一次再次插入时,继续转移旧哈希表中第一个不为空的桶,再插入元素。直至旧哈希表为空为止。这样一来,理想情况下,插入的时间复杂度是O(1)。

在Redis的实现中,新插入的键值对会放在箱子中链表的头部,而不是在尾部继续插入。

这种方案是基于两点考虑:
一是由于找到链表尾部的时间复杂度为O(n),且需要额外的内存地址来保存链表的尾部位置,而头插法的时间复杂度为O(1)。

二是处于Redis的实际应用场景来考虑。对于一个数据库系统来说,最新插入的数据往往更可能频繁地被获取,所以这样也能节省查找的耗时。

结束语

这次仅仅是简单介绍了一下Java、Redis中哈希表的不同扩容方案。
在学习过程中,有看到有一句话,感觉获益匪浅。即

没有完美的架构,只有满足需求的架构。

这种感受来自于:比如Java和Objective-C提供对isEqual()方法和hash()方法的重写,而没有提供一个通用的、默认的哈希函数,是考虑到了isEqual()方法可能会被重写。理由是,两个内存数据不同的对象,可能在语义上被视为相同的,而使用默认哈希函数得到的却是不同的哈希值,不符合我们的预期。

而Redis不支持重写哈希方法,因为它本身就是基于键值对的数据库,它的key值一般是可以唯一标识一个对象的。它不存在对象等同性的考虑,于是提供默认的哈希函数就可以了。

【数据结构之哈希表(二)】 哈希表的扩容实现机制相关推荐

  1. 数据结构与算法笔记(二)—— 顺序表

    一.顺序表的形式 在程序中,经常需要将一组(通常是同为某个类型的)数据元素作为整体管理和使用,需要创建这种元素组,用变量记录它们,传进传出函数等.一组数据中包含的元素个数可能发生变化(可以增加或删除元 ...

  2. 模板 - 数据结构 - ST表 + 二维ST表

    区间最大值,$O(nlogn)$ 预处理,$O(1)$ 查询,不能动态修改.在查询次数M显著大于元素数量N的时候看得出差距. 令 $f[i][j]$ 表示 $[i,i+2^j-1]$ 的最大值. 显然 ...

  3. JavaWeb开发之——DDL-操作表-查询表与创建表(07)

    一 概述 DDL-操作表(CRUD) 查询表 创建表 二 DDL-操作表(CRUD) 创建(Create) 查询(Retrieve) 修改(Update) 删除(Delete) 三 查询表 3.1 概 ...

  4. 高级数据结构与算法 | 哈希 :哈希冲突、负载因子、哈希函数、哈希表、哈希桶

    文章目录 哈希 哈希函数 常见的哈希函数 字符串哈希函数 哈希冲突 闭散列的解决方法 开散列的解决方法 负载因子以及增容 对于闭散列 对于开散列结构 具体实现 哈希表(闭散列) 插入 查找 删除 完整 ...

  5. ds哈希查找—二次探测再散列_大白话之哈希表和哈希算法

    哈希表概念 哈希表(散列表),是基于关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数(哈希函数 ...

  6. 数据结构学习笔记 哈希表(一) 哈希表基础与哈希函数

    ------HR:The first question is what you do if you have a conflict with your manager ? ------You:Hash ...

  7. 10_JavaScript数据结构与算法(十)哈希表

    JavaScript 数据结构与算法(十)哈希表 认识哈希表 哈希表是一种非常重要的数据结构,几乎所有的编程语言都直接或者间接应用这种数据结构. 哈希表通常是基于数组实现的,但是相对于数组,它存在更多 ...

  8. 【夜深人静写数据结构与算法 | 第八篇】哈希算法与哈希表

    目录 前言: 哈希: 哈希表: 哈希表组成: 哈希表实例: 哈希函数: TIPS: 总结 前言: 如果此时我要你默写一个有一百位的数字,你要如何做才能保证不会漏写呢?我们有一种方法很好用:直接数我们写 ...

  9. PAT甲级1145 Hashing - Average Search Time:[C++题解]哈希表、哈希表开放寻址法、二次探测法、求平均查找次数

    文章目录 题目分析 题目链接 题目分析 来源:acwing 本题的分析见另一道PAT的题目:PAT甲级1078 Hashing:[C++题解]哈希表.哈希表开放寻址法.二次探测法链接的题目就是让建立h ...

最新文章

  1. 从单体迈向 Serverless 的避坑指南
  2. mybatis jdbctype数据类型_mybaits-mybatis配置
  3. python怎么编程输入坐标_python编程之API入门: (一)使用百度地图API查地理坐标...
  4. MATLAB使用教程(三)——在文件中编程
  5. 如何使用strace+pstack利器分析程序性能
  6. 你以为熬个3年工作经验就是Java高级开发了?
  7. 决策者根据什么曲线做出决策_如何在开放社区中做出品牌决策
  8. 利用python提取abaqus节点坐标的脚本_用于在Abaqus中提取结点力的Python程序
  9. Flutter TextField设置默认值默认值和光标位置
  10. 提交太多oracle,急!!请教 用文本域向数据库oracle提交不了太多文字如何解决??...
  11. VC6生成Release版本程序
  12. 公交系统如何利用智能调度降低运营费用
  13. python的画图工具有哪些_python实现画图工具
  14. matlab二重积分有奇异点,用MATLAB计算某些区域上二重积分.pdf
  15. 内存颗粒和闪存颗粒的区别_什么是内存颗粒以及内存颗粒的种类和差别
  16. ubuntu14.04安装360随身wifi 2代
  17. 故意伤害罪具体会有什么处罚
  18. epub文件是什么文件?如何在windows系统上打开?
  19. 动物识别系统代码python_动物识别系统代码
  20. python使用pandas处理excel数据

热门文章

  1. 微信公众平台、微信公众平台.小程序、微信.开放平台三者关系及unionid
  2. 2022年墙壁挂架/电视支架怎么选?四大专业电视支架品牌介绍
  3. local function definitions are illegal
  4. java系统架构设计,2022最新
  5. Unity3d自学之路(一)
  6. 计算机复试题总结(九)
  7. 打开office(Word,Excel等)提示“应用程序无法正常启动(0xc0000142)。请单击确认关闭应用程序”
  8. 浪潮之巅第一章 — 帝国的余辉(ATT)
  9. 联想开机启动项按哪个_联想重装系统按哪个键|联想电脑重装系统按什么键
  10. 去除图片链接边框及其链接虚线