golang key map 所有_Map的底层实现 为什么遍历Map总是乱序的
Golang中Map的底层结构
其实提到Map,一般想到的底层实现就是哈希表,哈希表的结构主要是Hashcode + 数组。
- 存储kv时,首先将k通过hashcode后对数组长度取余,决定需要放入的数组的index
- 当数组对应的index已有元素时,此时产生一个【哈希冲突】。一般来说哈希冲突的解决方式为链表法,即在冲突的位置生成一个链表来存储元素
转自:https://www.jianshu.com/p/7aafee109f28
参考:go语言中文文档:www.topgoer.com
对于哈希表的具体实现和哈希冲突的解决方案这里就不赘述了,大家可以去看大神程序员小灰的漫画:什么是HashMap?。
Redis中的Dict结构(用于实现RedisDB、Hashmap,Set等)其实就是非常典型的哈希表。Golang中的Map结构与传统的哈希表稍有不同,它数组的基本单元并不是kv对,而是bucket。
每个bucket中可以放置8个kv对,这样可以减少对象的数量,有利于提升GC的性能;当bucket中放置不下时,会继续在溢出桶(overflow)中继续存储,串成一个链表的结构,如图所示:
链表法解决哈希冲突
Bucket中结构详解
// map 哈希表type Hmap struct{ count int // 元素数量 .... B uint8 // 哈希表数组的容量大小=2^B ... buckets unsafe.Pointer oldBuckets unsafe.Pointer // 用于扩容 ...}// Bucket 桶type bmap struct{ tophash [8]uint8 // 哈希值的高8位 keys values overflow *bmap}
- key的哈希值的低B位(比如数组大小为32,2^5=32,则B=5)决定了它放入buckets中的index位置
- key的哈希值的高8位放入bucket中的tophash字段中,因为每个bucket中最多可以放8个KV对,所以tophash为大小为8的数组:tophash字段可以用于加速key的比较,当在一个bucket中查询某key时,可以先比较哈希值的高8位是否符合,如果符合才会比较对应的key,这样加速了key比较的过程;
- keys和values为分开存储的,因为key和value可能是不同类型,比如map[int64]int8,分开存储不需要进行字节补齐,可以节省空间;
- overflow即溢出桶,用于存储哈希冲突后超过8对的kv对
扩容操作
负载因子=元素数量/数组大小
redis是1时扩容;golang因为数组中元素为bucket,每个bucket可以放8个kv,所以是在load factor = 6.5时才会触发扩容逻辑:
- 扩容时容量翻倍,申请一个新的数组,采用渐进式哈希的方式进行迁移(和redis类似,可以避免影响性能)
- 迁移过程中支持读写:
- 新增kv只写新表
- 修改和删除双写,保证新老表中的数据一致
- 读取时优先读老表,再读新表
- 迁移过程为每次update/remove操作时触发部分搬迁,每次搬迁2部分bucket中的数据:
- 修改的key所在的当前Bucket
- 按照顺序搬迁的一个Bucket(避免某些bucket一直未被访问导致无法搬迁成功)
- 直到所有数据搬迁完成后,删除oldBuckets,使得老哈希表中的Bucket对象被GC回收
遍历Map乱序之谜
在写代码时,当我们使用for k,v := range map {} 时会发现,每次输出的kv都是乱序的,既然map的底层是数组为什么不能按照固定顺序地输出呢?
结合上文我们说到扩容流程,由于扩容过程会新申请一个数组,并且将keys重新rehash后放入新的数组中。那么新的数组中的key的顺序就改变了,因此哈希表的底层实现使得map无法保证稳定地按照同一顺序输出keys。
Golang的作者为了”提醒“新手程序员不要依赖map遍历时返回的key顺序,采用随机选择遍历起始位置的方式使得遍历时返回是乱序的。
golang key map 所有_Map的底层实现 为什么遍历Map总是乱序的相关推荐
- 获取map中的一个value值以及遍历map获得map里所有key、value的值
前言: 1.声明一个map: Map map = new HashMap();2.向map中放值,注意:map是key-value的形式存放的.如: map.put("sa",&q ...
- java用循环给map里面存值_Java中如何遍历Map对象的4种方法
方法一 在for-each循环中使用entries来遍历 这是最常见的并且在大多数情况下也是最可取的遍历方式.在键值都需要时使用. 1 Map map = new HashMap();2 3 for ...
- Java遍历Map五种方法
一.Map集合遍历日常开发最常使用,简单总结五种方法差异. ①.Iterator+entrySet写法[推荐JDK8以下],Map.Entry是Map接口的内部接口,获取迭代器,然后依次取出每个迭代器 ...
- 【Groovy】map 集合 ( map 集合遍历 | 使用 map 集合的 find 方法遍历 map 集合 | 代码示例 )
文章目录 一.使用 map 集合的 find 方法遍历 map 集合 二.代码示例 一.使用 map 集合的 find 方法遍历 map 集合 使用 map 集合的 find 方法遍历 map 集合 ...
- 【Groovy】map 集合 ( map 集合遍历 | 使用 map 集合的 each 方法遍历 map 集合 | 代码示例 )
文章目录 一.使用 map 集合的 each 方法遍历 map 集合 二.代码示例 一.使用 map 集合的 each 方法遍历 map 集合 遍历 map 集合 , 可以调用 map 集合的 eac ...
- Java 遍历 Map 的几种方式
在 Java 中遍历 Map 有多种方法,既然 Java 中的所有 map 都实现了 Map 接口,以下方法适用于任何 map 实现(HashMap,TreeMap,LinkedHashMap,H ...
- Java遍历Map的五种方式
一.遍历Map的五种方式 java中遍历map一般有五种方法,从最早的Iterator,到java5支持的foreach,再到java8的Lambda表达式. 如果只是获取key,或者value,推荐 ...
- JS遍历Map集合方法 JS如何循环遍历后台传过来的Map?
HashMap集合经后台转为json字符串,并返回前台. JS接收到集合后可通过以下代码遍历: // 接收Map集合 var result = data.result;// 循环遍历Map集合 for ...
- java 循环遍历嵌套map_java循环遍历map的方法
//java HashMap 循环遍历map的方法 import java.util.ArrayList; import java.util.HashMap; import java.util.Ite ...
最新文章
- 优化 bulk insert
- IDE之VS:Visual Studio2017版本安装图文教程之详细攻略
- Java黑皮书课后题第2章:*2.8(当前时间)程序清单2-7给出了显示当前格林尼治时间的程序。修改这个程序,提示用户输入相对于GMT的时区偏移量,显示在这个特定时区的时间
- oracle trace发起用户,Oracle 使用TRACE进行SQL性能分析
- gwt格式_使用Spring Security保护GWT应用程序的安全
- 企业员工工资管理系统
- 对AngularJS的编译和链接过程讲解一步到位的文章
- 用大白话彻底搞懂 HBase RowKey 详细设计!
- python编程a的x次方_Python编程基础—运算符和math科学计算库
- java中的移位操作
- 从认知盈余说起,也谈分享精神
- 关于软件设计文档编写
- 计算机主板接口识别,电脑主板上接口怎么接 主板所有接口插线功能作用识别图解...
- ubuntu安装ROS
- nginx实现反向代理及负载均衡
- 矩阵分解及其Eigen实现
- 甲骨文收购mysql,甲骨文提出十大保证 承诺收购Sun后会善待MySQL
- Jmeter断言-响应断言
- 《自来水哲学-松下幸之助自传》读后感
- 绪论——信息理论学与量子信息学
热门文章
- Intellij Idea搭建Spark开发环境
- Java面试题库,java核心技术第十版下载
- [New Portal]Windows Azure Web Site (4) Web Site Gallery
- MySQL字段拼接Concat
- [Swift]LeetCode74. 搜索二维矩阵 | Search a 2D Matrix
- MySQL中CREATE DATABASE和CREATE SCHEMA区别(转)
- 解决三星手机EditText背景色的问题
- ms sql server 添加列,删除列。
- SharePoint视图过滤中People字段的局限
- 快速理解binary cross entropy 二元交叉熵