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。当发生哈希冲突时,可以减少比较次数和内存再分配

哈希冲突

解决哈希冲突的方法

  1. Open addressing

    1. Linear probing

      • 优点:简单
      • 缺点:有 clustering 趋势:当很多冲突发生在同一个哈希值上,周围多个位置就会被填充满,影响其他要插入的元素
    2. Rehash
      • 优点:在满足表大小为素数的条件下可以解决 clustering 问题
      • 缺点:需要计算两次
    3. Quadratic probing
      • Linear probing 的变体
  2. Chaining
    • 优点:不影响其他位置的元素
    • 缺点:
      1. 当很多冲突发生在同一个哈希值上,那么搜索时复杂度就会退化到 O(n),这和 clustering 的影响一致
      2. 如果链表节点都是从堆中分配,可能造成内存碎片化
补充说明
  1. 一次迁移操作是将正常桶以及它的溢出桶全部迁移,每次扩容进行 1-2 次这样的操作
  2. 只有在赋值(mapassign)删除键值对(mapdelete)时才会继续扩容(如果需要的话),在访问数据(mapaccess)时是不会进行扩容操作的
疑问

map 在判断并发读写上使用 hashWriting 来判断,但这个还是有几率漏判的吧?

【待补充】

可能会补充。。。

  1. 扩容细节
  2. 迁移细节
  3. 应用于 for range 的 iterator
  4. 对于计算溢出桶的细节没太看懂,没看懂为什么 - 8

【小记】golang_map的更多相关文章

  1. [原]Paste.deploy 与 WSGI, keystone 小记

    Paste.deploy 与 WSGI, keystone 小记 名词解释: Paste.deploy 是一个WSGI工具包,用于更方便的管理WSGI应用, 可以通过配置文件,将WSGI应用加载起来. ...

  2. MySql 小记

    MySql  简单 小记 以备查看 1.sql概述 1.什么是sql? 2.sql发展过程? 3.sql标准与方言的关系? 4.常用数据库? 5.MySql数据库安装? 2.关键概念 表结构----- ...

  3. Git小记

    Git简~介 Git是一个分布式版本控制系统,其他的版本控制系统我只用过SVN,但用的时间不长.大家都知道,分布式的好处多多,而且分布式已经包含了集中式的几乎所有功能.Linus创造Git的传奇经历就 ...

  4. 广州PostgreSQL用户会技术交流会小记 2015-9-19

    广州PostgreSQL用户会技术交流会小记 2015-9-19 今天去了广州PostgreSQL用户会组织的技术交流会 分别有两个session 第一个讲师介绍了他公司使用PostgreSQL-X2 ...

  5. 东哥读书小记 之 《MacTalk人生元编程》

         一直以来的自我感觉:自己是个记性偏弱的人.反正从小读书就喜欢做笔记(可自己的字写得巨丑无比,尼玛不科学呀),抄书这事儿真的就常发生俺的身上. 因为那时经常要背诵课文之类,反正为了怕自己忘记, ...

  6. Paypal支付小记

    Paypal支付小记 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !impo ...

  7. linux 下cmake 编译 ,调用,调试 poco 1.6.0 小记

    上篇文章 小记了: 关于 Poco::TCPServer框架 (windows 下使用的是 select模型) 学习笔记. http://www.cnblogs.com/bleachli/p/4352 ...

  8. mongodb入门学习小记

    Mongodb 简单入门(个人学习小记) 1.安装并注册成服务:(示例) E:\DevTools\mongodb3.2.6\bin>mongod.exe --bind_ip 127.0.0.1 ...

  9. 【日常小记】统计后缀名为.cc、.c、.h的文件数【转】

    转自:http://www.cnblogs.com/skynet/archive/2011/03/29/1998970.html 在项目开发时,有时候想知道源码文件中有多少后缀名为.cc..c..h的 ...

  10. ulua 路径小记 以及 lua require 机制整理

    ulua 路径小记 在学习ulua时,require模块的根路径可以为项目的Lua文件夹或者ToLua文件夹(Editor下),但是在package.path和package.cpath中并没有看到当 ...

随机推荐

  1. elasticsearch相关概念及常用操作汇总

    背景 我本来是想把我的写的es的平时总结dsl发出来的,但是我发现只搞那个意义大不.干脆多写点吧. 索引的结构化和非结构 我们经常用数据库,当然会经常用到索引. 然后从索引的维度去分析,系统分为结构化 ...

  2. pdf.js 跨域完美解决!

    在网上查看很多方法去解决此类跨域问题,及如何动态加载pdf文件.看来看去 请求的由后台处理加header头的  pdf.js 自带的 获取地址栏param参数值的 都是很麻烦的步骤并且有时不能有效解决 ...

  3. adb server version (36) doesn‘t match this client (41)解决

    问题描述:夜神模拟器,dos窗口下,然后adb devices发现连不上模拟器了,报adb server version (36) doesn't match this client (41); ki ...

  4. 2023 年 CCF 春季测试赛模拟赛 - 1

    T1 个人思路: 询问:求 \(1\) 到 \(t_i\) 路径上离 \(1\) 最远的 \(p\),使得 \(dis_{1,p} \times 2 \le d_i\).即 \(dis_{1,t} \ ...

  5. 1223. 掷骰子模拟 (Hard)

    问题描述 1223. 掷骰子模拟 (Hard) 有一个骰子模拟器会每次投掷的时候生成一个 1 到 6 的随机数. 不过我们在使用它时有个约束,就是使得投掷骰子时, 连续 掷出数字 i 的次数不能超过 ...

  6. K8s集群版本升级

    k8s组件升级流程: 升级主管理节点→升级其他管理节点→升级工作节点 首先备份主管理节点的etcd,检查版本号,为了保证版本的兼容性,跨度最好不要超过两个版本. [root@master ~]# ku ...

  7. h5端安装调试工具,react版(但不限于react,vue等)

    首先 npm install vconsole -S 其次在非生产环境的时候可以使用 import VConsole from 'vconsole'; const vConsole = new VCo ...

  8. 如何将多个TXT合并成一个TXT,文件名称提取

    方法1:1.将所有需要合并的TXT整理到一个文件夹中,切记,TXT合并最好每个TXT内容头或尾留一行间距,因为合并是直接合并,不会保留间距. 2.使用Windows命令cmd,切换到文件所在文件夹 3 ...

  9. vue2 项目引入Fontawesome

    官网: https://fontawesome.com/ 1.安装 `` `powershell npm i --save @fortawesome/fontawesome-svg-core Usin ...

  10. C++数组(一):一维数组

    C++一维数组 C++数组的定义方式 数据类型 数组名[数组长度]; 例子:int arr[3]; arr[0] = 1; arr[1] = 2; arr[2] = 3; 数据类型 数组名[数组长度] ...