【小记】golang_map
map
前言:map 几个操作实现有点复杂,即便之前看懂了没过多久也就忘了,这里简单做下记录。为了便于记忆,将 mapassign 的全过程以流程图的方式展示,其他省略
mapassign
在流程图中可以看到 golang map 在扩容时采取渐进式扩容的策略
mapaccess
- mapaccess1: 用于类似于
val := h[key]
只需一个返回值的形式 - mapaccess2: 用于类似于
val, exsit := h[key]
- mapaccessK: 用于
for range
且特殊时期 - mapaccess_fast<T>: 对 key 为特定类型的优化处理
返回 value 类型的零值
if h == nil || h.count == 0 {
...
return unsafe.Pointer(&zeroVal[0])
}
寻找对应的桶
if c := h.oldbuckets; c != nil {
// 如果存在旧桶数据,说明当前在扩容阶段,且可能查找的数据还未迁移,需要先从旧桶找起
if !h.sameSizeGrow() {
// 一般扩容时,会将容量扩大两倍,因此需要按照之前的数量重新计算哈希值
m >> 1
}
oldb := (*bmap)(add(c, (hash&m)*uintptr(t.bucketsize)))
if !evacuated(oldb) {
// 在 mapassign 的流程图中可以看到,迁移是一个桶带着它的溢出桶全部迁移的
// 所以如果该桶未发生迁移,就在当前桶以及其溢出桶查找即可
b = oldb
}
}
该处可以看到 mapaccess 与 mapassign 在扩容时查找数据的方式是有区别的。mapassign 会先迁移数据再去新桶查找,而 mapaccess 会在新旧桶中抉择
mapdelete
空表删键值对
if h == nil || h.count == 0 {
...
return
}
和 mapaccess 一样,对值为 nil 或不含任何键值对的 map 都可以操作。而 mapassign 不能对值为 nil 的 map 操作
重置状态
当成功删除一个键值对后,该 entry 的 tophash 被置为 emptyOne
// 注:进入后面步骤的前提是已经找到了需要删除的键值对,代码已省略
b.tophash[i] = emptyOne
if i == bucketCnt-1 { // 如果是当前桶最后一个 entry
if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
// 下一个溢出桶首个 tophash 不为 emptyRest,停止搜索
goto notLast
}
} else {
if b.tophash[i+1] != emptyRest {
// 下一个 entry 的 tophash 不为 emptyRest, 停止搜索
goto notLast
}
}
for {
// 当前 entry 状态为 emptyOne,且后置位状态为 emptyRest,更新当前位置状态
b.tophash[i] = emptyRest
if i == 0 {
// 当前桶以及其后溢出桶都空了
if b == bOrig {
// 若当前桶为第一个桶,代表已经回到初始桶,直接退出即可
break
}
// 寻找前一个桶,并从最后一个 entry 继续
c := b
for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {}
i = bucketCnt - 1
} else {
// 倒序检查之前的 entry 的状态是否需要更改
i--
}
if b.tophash[i] != emptyOne {
// 遇到有存 key/value 的 entry 时结束
break
}
}
mapclear
停止扩容
h.flags &^ = sameSizeGrow // 停止等量扩容
h.oldbuckets = nil // 停止扩容
h.nevacuate = 0 // 停止迁移
清除内存
// makeBucketArray clears the memory pointed to by h.buckets
// and recovers any overflow buckets by generating them
// as if h.buckets was newly alloced.
// If dirtyalloc is nil a new backing array will be alloced and
// otherwise dirtyalloc will be cleared and reused as backing array.
mapclear 会保留正常桶,但是会清除溢出桶以及正常桶桶内的键值对,并且会根据正常桶数量决定是否重新生成溢出桶。也就是说,mapclear 类似于清空数据的等量扩容,但是并不会真的清空所有存储空间。
因此,如果当你想通过清除所有数据来减小 map 的内存占用,应该使用 make
重新赋值
tophash
tophash 不仅记录哈希值的高 8 位,还记录当前位置的状态值,例如:
- 当前位置的 key/value 被删除后,tophash 被置为 emptyOne,若后面的位置都为空,则进一步更新为 emptyRest。当发生哈希冲突时,可以减少比较次数和内存再分配
哈希冲突
解决哈希冲突的方法:
- Open addressing
- Linear probing
- 优点:简单
- 缺点:有 clustering 趋势:当很多冲突发生在同一个哈希值上,周围多个位置就会被填充满,影响其他要插入的元素
- Rehash
- 优点:在满足表大小为素数的条件下可以解决 clustering 问题
- 缺点:需要计算两次
- Quadratic probing
- Linear probing 的变体
- Linear probing
- Chaining
- 优点:不影响其他位置的元素
- 缺点:
- 当很多冲突发生在同一个哈希值上,那么搜索时复杂度就会退化到 O(n),这和 clustering 的影响一致
- 如果链表节点都是从堆中分配,可能造成内存碎片化
补充说明
- 一次迁移操作是将正常桶以及它的溢出桶全部迁移,每次扩容进行 1-2 次这样的操作
- 只有在赋值(mapassign)和删除键值对(mapdelete)时才会继续扩容(如果需要的话),在访问数据(mapaccess)时是不会进行扩容操作的
疑问
map 在判断并发读写上使用 hashWriting
来判断,但这个还是有几率漏判的吧?
【待补充】
可能会补充。。。
- 扩容细节
- 迁移细节
- 应用于 for range 的 iterator
- 对于计算溢出桶的细节没太看懂,没看懂为什么 - 8
【小记】golang_map的更多相关文章
- [原]Paste.deploy 与 WSGI, keystone 小记
Paste.deploy 与 WSGI, keystone 小记 名词解释: Paste.deploy 是一个WSGI工具包,用于更方便的管理WSGI应用, 可以通过配置文件,将WSGI应用加载起来. ...
- MySql 小记
MySql 简单 小记 以备查看 1.sql概述 1.什么是sql? 2.sql发展过程? 3.sql标准与方言的关系? 4.常用数据库? 5.MySql数据库安装? 2.关键概念 表结构----- ...
- Git小记
Git简~介 Git是一个分布式版本控制系统,其他的版本控制系统我只用过SVN,但用的时间不长.大家都知道,分布式的好处多多,而且分布式已经包含了集中式的几乎所有功能.Linus创造Git的传奇经历就 ...
- 广州PostgreSQL用户会技术交流会小记 2015-9-19
广州PostgreSQL用户会技术交流会小记 2015-9-19 今天去了广州PostgreSQL用户会组织的技术交流会 分别有两个session 第一个讲师介绍了他公司使用PostgreSQL-X2 ...
- 东哥读书小记 之 《MacTalk人生元编程》
一直以来的自我感觉:自己是个记性偏弱的人.反正从小读书就喜欢做笔记(可自己的字写得巨丑无比,尼玛不科学呀),抄书这事儿真的就常发生俺的身上. 因为那时经常要背诵课文之类,反正为了怕自己忘记, ...
- Paypal支付小记
Paypal支付小记 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !impo ...
- linux 下cmake 编译 ,调用,调试 poco 1.6.0 小记
上篇文章 小记了: 关于 Poco::TCPServer框架 (windows 下使用的是 select模型) 学习笔记. http://www.cnblogs.com/bleachli/p/4352 ...
- mongodb入门学习小记
Mongodb 简单入门(个人学习小记) 1.安装并注册成服务:(示例) E:\DevTools\mongodb3.2.6\bin>mongod.exe --bind_ip 127.0.0.1 ...
- 【日常小记】统计后缀名为.cc、.c、.h的文件数【转】
转自:http://www.cnblogs.com/skynet/archive/2011/03/29/1998970.html 在项目开发时,有时候想知道源码文件中有多少后缀名为.cc..c..h的 ...
- ulua 路径小记 以及 lua require 机制整理
ulua 路径小记 在学习ulua时,require模块的根路径可以为项目的Lua文件夹或者ToLua文件夹(Editor下),但是在package.path和package.cpath中并没有看到当 ...
随机推荐
- elasticsearch相关概念及常用操作汇总
背景 我本来是想把我的写的es的平时总结dsl发出来的,但是我发现只搞那个意义大不.干脆多写点吧. 索引的结构化和非结构 我们经常用数据库,当然会经常用到索引. 然后从索引的维度去分析,系统分为结构化 ...
- pdf.js 跨域完美解决!
在网上查看很多方法去解决此类跨域问题,及如何动态加载pdf文件.看来看去 请求的由后台处理加header头的 pdf.js 自带的 获取地址栏param参数值的 都是很麻烦的步骤并且有时不能有效解决 ...
- adb server version (36) doesn‘t match this client (41)解决
问题描述:夜神模拟器,dos窗口下,然后adb devices发现连不上模拟器了,报adb server version (36) doesn't match this client (41); ki ...
- 2023 年 CCF 春季测试赛模拟赛 - 1
T1 个人思路: 询问:求 \(1\) 到 \(t_i\) 路径上离 \(1\) 最远的 \(p\),使得 \(dis_{1,p} \times 2 \le d_i\).即 \(dis_{1,t} \ ...
- 1223. 掷骰子模拟 (Hard)
问题描述 1223. 掷骰子模拟 (Hard) 有一个骰子模拟器会每次投掷的时候生成一个 1 到 6 的随机数. 不过我们在使用它时有个约束,就是使得投掷骰子时, 连续 掷出数字 i 的次数不能超过 ...
- K8s集群版本升级
k8s组件升级流程: 升级主管理节点→升级其他管理节点→升级工作节点 首先备份主管理节点的etcd,检查版本号,为了保证版本的兼容性,跨度最好不要超过两个版本. [root@master ~]# ku ...
- h5端安装调试工具,react版(但不限于react,vue等)
首先 npm install vconsole -S 其次在非生产环境的时候可以使用 import VConsole from 'vconsole'; const vConsole = new VCo ...
- 如何将多个TXT合并成一个TXT,文件名称提取
方法1:1.将所有需要合并的TXT整理到一个文件夹中,切记,TXT合并最好每个TXT内容头或尾留一行间距,因为合并是直接合并,不会保留间距. 2.使用Windows命令cmd,切换到文件所在文件夹 3 ...
- vue2 项目引入Fontawesome
官网: https://fontawesome.com/ 1.安装 `` `powershell npm i --save @fortawesome/fontawesome-svg-core Usin ...
- C++数组(一):一维数组
C++一维数组 C++数组的定义方式 数据类型 数组名[数组长度]; 例子:int arr[3]; arr[0] = 1; arr[1] = 2; arr[2] = 3; 数据类型 数组名[数组长度] ...