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. 一文总结Vue

    一.创建项目 1.安装Node.js 查看node版本 node-v 查看npm版本 npm-v 2.安装vue-cli脚手架 安装 npm install -g @vue/cli 创建项目 vue ...

  2. vue开发中,数据更新,但视图不刷新

    我们在开发过程中会碰到数据更新,但是视图并未改变的情况,情况如下: 第一种:动态给对象新增属性或者删除属性是不会触发视图刷新的,Vue识别不到: 第二种:通过数组下标修改数组中的元素或者手动修改数组的 ...

  3. Springboot 拦截器的配置

    在Springboot项目中添加拦截器,分两步: 1:创建一个拦截器类 2:配置拦截器 以上步骤完成就可以使用了,下面来添加拦截器: 1:创建一个拦截器类 MyInteceptor 继承 Handle ...

  4. 复习第二点-2.基于注解的helloworld

    web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="htt ...

  5. composer 换源

    #首先把默认的源给禁用掉 composer config -g secure-http false #再修改镜像源 这里我使用阿里的源 composer config -g repo.packagis ...

  6. 容器之docker基础

    一 docker的介绍 Docker是一个开源的应用容器引擎,使用Go语言开发,基于Linux内核的cgroup, namespace, Union FS等技术,对应用进程进行封装隔离,并且独立于宿主 ...

  7. vue 滑动到指定位置

    在Vue中,有三种方式可以实现H5页面滑动至指定位置 方法1: //先获取目标位置距离 mounted() { this.$nextTick(() => { setTimeout(() => ...

  8. 通过parameters用python造数据

    1.先通过命令安装allpairs库.allpairspy,命令为:pip install allpairs.pip install parameters,安装成功 2.通过python去写出来,如图 ...

  9. usbip 非源码安装

    对于很多 Linux 发行版不能直接搜索 usbip 的包名进行安装 Kylin OS 其实他是包含在一个名为 linux-tools-generic 的软件包中的 对于 UOS 操作系统可以直接安装 ...

  10. Linux系统目录及说明

    Linux系统目录结构以及简单说明: (1)/:根目录 (2)/boot:很多Linux系统把内核映像和其他一些和启动有关的文件都放在这里. (3)/tmp:一般只是启动时产生的临时文件才会放在这个地 ...