【小记】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中并没有看到当 ...
随机推荐
- 【最新最新】mac pro 安装扩展imagick 最新总结
近期在mac上做相关开发,关于验证码图片如果没装imagick扩展会报 GD with FreeType or ImageMagick PHP extensions are required.等错误 ...
- lg8936题解
虽然这道题的题目标签有颜色段均摊和并查集,但是这道题的做法与这两个算法并无关系. 考虑从询问的右往左扫描数组.设\(g_i\)表示以第\(i\)个询问为左端点,最大的\(g_i\)令\(sol(1,n ...
- soursetree 关于https:git remote: Unauthorized和username和password修改
一.sourcetree推送代码提交不上提示https:git remote: Unauthorized由于没有权限,需要登陆正确的账号以及密码即可以提交 二.SourceTree这是一个无效源路径/ ...
- input输入框限制输入
<input type="password" value="" id="pwd" class="Rectangle-1188 ...
- .NetCore2.0引用DLL报System.InvalidOperationException: Can not find compilation library location for package 'XXX'
.NET CORE 2.0 MVC项目引用类库出现:System.InvalidOperationException: Can not find compilation library locatio ...
- QML笔记——MouseArea的覆盖区域
问题摘要: 初学QML,今天发现一个奇怪的现象,调用MouseArea的位置不同,结果不同. 1.一个简单的qml示例 Rectangle { id: root width: 320 height: ...
- Mac 远程 屏幕共享 screen sharing
屏幕共享可以用于在局域网中控制另一台 Mac,也能通过 iMessage 在广域网环境下创建彼此的连接,用来指导和解决问题非常方便. 通过 Apple ID 来创建连接 1,Command+空格键 ...
- vue 3.0 总线程bus引入(mitt)
vue 3.0 移除了 $on,$off 和 $once 方法,$emit 仍然是现有 API 的一部分,因为它用于触发由父组件以声明方式附加的事件处理程序. 官方推荐使用第三方类库. mitt举例 ...
- toast简介
PostgreSQL数据库不允许元组(行,记录)跨越多个页面(page)存储,所以,它不能直接存储非常大的字段值.对于大字段值,它将被压缩且(或)分解为多个物理行,该技术称为"TOAST&q ...
- npm升级报错,没有权限.ERRERR!The operation was rejected by your operating system. npm ERR!Error: EPERM: operation not permitted, rename
问题描述 Windows system32>npm installg npm 2335 error code EEXIST2336 error path D:\Program Files\nod ...