文章参考:Go语言设计与实现3.3 哈希表

哈希表的意义不言而喻,它能提供 O(1) 复杂度的读写性能,所以主流编程语言中都内置有哈希表。

哈希表的关键在于哈希函数, 好的哈希函数能减少哈希碰撞,提供最优秀的读写性能。

哈希碰撞

因为没有完美的哈希函数, 所以哈希碰撞不可避免,一般有开放寻址法和拉链法,其中拉链法是主流

  • 开放寻址法:当向哈希表写入新的数据时,如果发生了冲突,就会将键值对写入到下一个索引不为空的位置

  • 拉链法:拉链法一般使用数组和链表组成,数据经过哈希函数得到一个桶时,先遍历桶中的链表,存在相同的键值对,则更新,不存在则在链表末尾追加新键值对

Go 表示哈希表的数据结构

type hmap struct {
// 表示哈希表中元素的数量
count int
flags uint8 // 表示哈希表中桶的数量, len(buckets) = 2^B
B uint8
noverflow uint16 // hash函数的种子
hash0 uint32 buckets unsafe.Pointer // 用于在扩容时保存之前 buckets
// 因为每次扩容都是2的倍数,所以 bucket = 2oldbuckets
oldbuckets unsafe.Pointer
nevacuate uintptr extra *mapextra
} type mapextra struct {
overflow *[]*bmap
oldoverflow *[]*bmap
nextOverflow *bmap
}

哈希表 hmap 的桶是 bmap,每个 bmap 都能存储 8 个键值对,单个桶装满时会使用 nextOverflow 桶存储溢出的数据

type bmap struct {
// 存储了键的哈希的高 8 位
// 通过比较不同键的哈希的高 8 位可以减少访问键值对次数以提高性能
tophash [bucketCnt]uint8
}

访问 map 中的数据

如上图所示,每一个桶都是一整片的内存空间,当发现桶中的 tophash 与传入键的 tophash 匹配之后,我们会通过指针和偏移量获取哈希中存储的键 keys[0] 并与 key 比较,如果两者相同就会获取目标值的指针 values[0] 并返回

向 map 写入数据

函数会根据传入的键拿到对应的哈希和桶,通过遍历比较桶中存储的 tophash 和键的哈希,如果找到了相同结果就会返回目标位置的地址,获得目标地址之后会通过算术计算寻址获得键值对 k 和 val, 如果当前键值对在哈希中不存在,哈希会为新键值对规划存储的内存地址,这期间只会返回内存地址,真正的赋值操作是在编译期间插入的。

00018 (+5) CALL runtime.mapassign_fast64(SB)
00020 (5) MOVQ 24(SP), DI ;; DI = &value
00026 (5) LEAQ go.string."88"(SB), AX ;; AX = &"88"
00027 (5) MOVQ AX, (DI) ;; *DI = AX

我们通过 LEAQ 指令将字符串的地址存储到寄存器 AX 中,MOVQ 指令将字符串 "88" 存储到了目标地址上完成了这次哈希的写入

扩容

随着哈希表中元素的逐渐增加,哈希表的性能会逐渐恶化,当装载因子 > 6.5 时, 或者 哈希表创建了太多的溢出桶, 会触发扩容

装载因子 = 元素数量 / 桶数量

哈希表在扩容的过程中会创建一组新桶和溢出桶,随后将原油的桶数组设置到 oldbuckets 上,将新桶设置到 buckets 上,新计算旧桶内元素的哈希到新桶上,

在扩容期间访问哈希表时会使用旧桶,整个期间元素再分配的过程也是在调用写操作时增量进行的,不会造成性能的瞬时巨大抖动

深入理解 Go Map的更多相关文章

  1. 我所理解Java集合框架的部分的使用(Collection和Map)

    所谓集合,就是和数组类似——一组数据.java中提供了一些处理集合数据的类和接口,以供我们使用. 由于数组的长度固定,处理不定数量的数据比较麻烦,于是就有了集合. 以下是java集合框架(短虚线表示接 ...

  2. MapReduce剖析笔记之五:Map与Reduce任务分配过程

    在上一节分析了TaskTracker和JobTracker之间通过周期的心跳消息获取任务分配结果的过程.中间留了一个问题,就是任务到底是怎么分配的.任务的分配自然是由JobTracker做出来的,具体 ...

  3. Hadoop 2.4.1 Map/Reduce小结【原创】

    看了下MapReduce的例子.再看了下Mapper和Reducer源码,理清了参数的意义,就o了. public class Mapper<KEYIN, VALUEIN, KEYOUT, VA ...

  4. java2集合框架的一些个人分析和理解

    Java2中的集合框架是广为人知的,本文打算从几个方面来说说自己对这个框架的理解. 下图是java.util.Collection的类图(基本完整,有些接口如集合类均实现的Cloneable.Seri ...

  5. Map实现之HashMap(结构及原理)(转)

    java.util包中的集合类包含 Java 中某些最常用的类.最常用的集合类是 List 和 Map.List 的具体实现包括 ArrayList 和 Vector,它们是可变大小的列表,比较适合构 ...

  6. map & flatMap 浅析

    我之前一直以为我是懂 map 和 flatMap 的.但是直到我看到别人说:「一个实现了 flatMap 方法的类型其实就是 monad.」我又发现这个熟悉的东西变得陌生起来,本节烧脑体操打算更细致一 ...

  7. ReactiveCocoa源码解析(五) SignalProtocol的observe()、Map、Filter延展实现

    上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于<Signal中的静态属性静态方法以及面向协议扩展>.并且聊了Signal的所有的g功能扩展 ...

  8. 深入理解HashMap的扩容机制

    什么时候扩容: 网上总结的会有很多,但大多都总结的不够完整或者不够准确.大多数可能值说了满足我下面条件一的情况. 扩容必须满足两个条件: 1. 存放新值的时候当前已有元素的个数必须大于等于阈值 2. ...

  9. ReactiveSwift源码解析(五) SignalProtocol的observe()、Map、Filter延展实现

    上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于<Signal中的静态属性静态方法以及面向协议扩展>.并且聊了Signal的所有的g功能扩展 ...

随机推荐

  1. jmeter中beanshell postprocessor结合fastjson库提取不确定个数的json参数

    在项目实践中,遇到了这样一个问题.用jmeter作http接口测试,需要的接口参数个数是不确定的.也就是说,在每次测试中,根据情况不同,可能页面中的列表中所含的参数个数是不确定的,那么要提取的参数个数 ...

  2. kvm虚拟机管理(3)

    一.远程管理kvm虚拟机 (1)上一节我们通过 virt-manager 在本地主机上创建并管理 KVM 虚机.其实 virt-manager 也可以管理其他宿主机上的虚机.只需要简单的将宿主机添加进 ...

  3. 加载动画效果 HTML+ CSS

    加载动画效果 写在前面 在无限的时间的河流里,人生仅仅是微小又微小的波浪.--郭小川 实现效果 实现原理 通过2个伪元素来设置3条颜色边框 通过定位将3个圆弧边框层叠再一起,再通过旋转实现一个圆的效果 ...

  4. javascript获取日期,年月,日

    <SCRIPT LANGUAGE="JavaScript"> var myDate = new Date();     myDate.getYear();       ...

  5. Spring的controller接受Date类型数据,接受枚举类型数据

    1. Controller接收Date类型的数据 核心使用@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 来将传递过来的时间字符串 ...

  6. 4D雷达成像技术

    4D雷达成像技术 当我们谈及3D捕捉时,总是先想到光学传感器.当我们讨论在第四维度(时间)讨论视觉数据时,倾向于考虑场景数据调度.这些是我们多年来关注激光雷达(LiDAR)和摄影测量,以及用户针对缓慢 ...

  7. AlexeyAB DarkNet YOLOv3框架解析与应用实践(六)

    AlexeyAB DarkNet YOLOv3框架解析与应用实践(六) 1. Tiny Darknet 听过很多人谈论SqueezeNet. SqueezeNet很酷,但它只是优化参数计数.当大多数高 ...

  8. 小白自制Linux开发板 一. 瞎抄原理图与乱画PCB

    因为墨云是基于高中物理水平的电路知识来学习.而且此前也就玩过树莓派.Esp8266之类的开发板,水平基础趋近于零,所以在写这个系列的时候抱着记录的心态.还望不足之处还望大佬们指正. <论语> ...

  9. Ucore lab1实验报告

    练习一 Makefile 1.1 OS镜像文件ucore.img 是如何一步步生成的? + cc kern/init/init.c + cc kern/libs/readline.c + cc ker ...

  10. th:insert、th:replace、th:include抽取和引用页面公共片段、传参等

    一.抽取公共片段 th:fragment  给片段命名 将公共片段抽取出来,并在顶级标签内使用th:fragment给该片段命名. 例如:将公共片段抽取出来放到comment/bar.html中: & ...