map的自动扩容与手动缩容
map的自动扩容与手动缩容
首先还是提出问题:扩容和缩容有什么用?为什么需要扩容和缩容?
在想解答这个问题之前,首先还是需要了解一下go语言中的map
go语言中的map与Java中的map实现还是有些不同,go的map底层实现方式是hash表(哈希桶+数组),Java中,JDK1.6,JDK1.7里HashMap采用位桶+链表实现,JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树。
先看map的数据结构吧:
const (
bucketCntBits = 3
bucketCnt = 1 << bucketCntBits // 一个桶最多存储8个key-value对
loadFactorNum = 13 // 扩散因子:loadFactorNum / loadFactorDen = 6.5。触发扩容
loadFactorDen = 2
)
type hmap struct {
count int
flags uint8 // 记录几个特殊的位标记,如当前是否有别的线程正在写map、当前是否为相同大小的增长(扩容/缩容?)
B uint8 // hash桶buckets的数量为2^B个
noverflow uint16
hash0 uint32 // hash种子
buckets unsafe.Pointer // 指向2^B个桶组成的数组的指针,数据存在这里
oldbuckets unsafe.Pointer
nevacuate uintptr // 计数器,标示扩容后搬迁的进度
extra *mapextra // 保存溢出桶的链表和未使用的溢出桶数组的首地址
}
type mapextra struct {
overflow *[]*bmap
oldoverflow *[]*bmap
nextOverflow *bmap //保存为使用的数组桶地址
}
type bmap struct {
tophash [8]uint8 //存储哈希值的高8位
data byte[1] //key value数据:key/key/key/.../value/value/value... 如此存放是为了节省 字节对齐带来的空间浪费。
overflow *bmap //溢出bucket的地址
}
以上面的代码来说说吧,hmap是map的数据结构,bmap是真正用来存放数据的地方,一个bmap内能够存放8个键值对(很神奇吧,很多语言的阈值为8,这是一个神奇的数字),tophash有8个字节,这是用来干嘛的,总要区分一下桶里的key吧,data是数据,overflow是来干嘛的?也是用来存数据,这里不要和extra弄混,extra预留的桶,可以被用来作为overflow,溢出桶的具体情况下面来具体说明一下:(先给图:网上随便找的不知道是谁的了)
hmap的B为1,那么进行初始化的时候会生成2^B个桶(bucket1,bucket2)
先存入8个key-value,根据一些列的hash算法,然后很不巧的这8个都被存入到了bucket1里面去了
现在又有一个key-value来了,根据hash算法,它应该又被分配到了b1里面去,但是最多存8个,现在已经有8个了,这时候怎么办,重新一个hash算法将所有的数据再次分配,不好意思,我也不知道为什么不行,大概没必要,且有根好的做法
在原本的bucket1后面再建一个bucket1*,再将这个key-value存进去。
上面至于为什么不进行扩容,没办法,没满足要求:
func overLoadFactor(count int, B uint8) bool {
return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}
map的数据量count大于(2^B)*6.5。注意这里不包括溢出的桶。
这里补充插入时的一些操作:
if h.flags&hashWriting != 0 {
throw("concurrent map writes")
}
这就是为什么同时读写map为什么会报错的原因了,加锁就行。
基本了解了数据结构,接着说扩容,还是上面的例子,B=1,扩容因为6.5,当key-value的数量达到13的时候(包括已经删除的),会触发扩容,B=B+1,容量扩大了一倍。为什么会包括已经删除的,其实这里描述的不太准确,mapdelete里面的具体操作是这样的:
当这个被删除的key不是当前桶(包括溢出桶)里面的最后一个有效key时,则只置emptyOne标志,该位置被删除但未内存没有被释放,后续插入操作不能使用此位置
如果只剩最后一个有效节点了也被删除了,则把桶里面所有标志为emptyOne的位置,都置为emptyRest。置为emptyRest的位置可以在后续的插入操作中被使用。
为什么这么做:
这种删除方式,以少量空间来避免桶链表和桶内的数据移动。事实上,go 数据一旦被插入到桶的确切位置,map是不会再移动该数据在桶中的位置了。
那么这个删除模式会导致一种情况,就是每个桶里面只有一个数据,造成了很大的空间浪费了,想想map只增不减情况,这时候就需要缩容了。go里面也有缩容判断,不过这个缩容是伪缩容,
func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {
if B > 15 {
B = 15
}
return noverflow >= uint16(1)<<(B&15) //溢出的桶数量noverflow>=32768(1<<15)
}
只有溢出桶太多才会缩容,不过内存大小并不会发生变化,至于为什么不会变化,因为B没有变,想要真正实现内存的优化也是可行的,不过并不会太优雅。这时候就需要我们需要手动缩容了,说这么多,其实代码很简单:
oldMap := make(map[int]int, 100000) newMap := make(map[int]int, len(oldMap))
for k, v := range oldMap {
newMap[k] = v
}
oldMap = newMap
map还有一个比较有意思的是for range,可以了解一下
map的自动扩容与手动缩容的更多相关文章
- 023.掌握Pod-Pod扩容和缩容
一 Pod的扩容和缩容 Kubernetes对Pod的扩缩容操作提供了手动和自动两种模式,手动模式通过执行kubectl scale命令或通过RESTful API对一个Deployment/RC进行 ...
- Redis Cluster 自动化安装,扩容和缩容
Redis Cluster 自动化安装,扩容和缩容 之前写过一篇基于python的redis集群自动化安装的实现,基于纯命令的集群实现还是相当繁琐的,因此官方提供了redis-trib.rb这个工具虽 ...
- k8s Pod 扩容和缩容
在生产环境下,在面临服务需要扩容的场景时,可以使用Deployment/RC的Scale机制来实现.Kubernetes支持对Pod的手动扩容和自动扩容. 手动扩容缩容 通过执行扩容命令,对某个dep ...
- 数据结构 5 哈希表/HashMap 、自动扩容、多线程会出现的问题
上一节,我们已经介绍了最重要的B树以及B+树,使用的情况以及区别的内容.当然,本节课,我们将学习重要的一个数据结构.哈希表 哈希表 哈希也常被称作是散列表,为什么要这么称呼呢,散列.散列.其元素分布较 ...
- 如何根据不同业务场景调节 HPA 扩缩容灵敏度
背景 在 K8s 1.18 之前,HPA 扩容是无法调整灵敏度的: 对于缩容,由 kube-controller-manager 的 --horizontal-pod-autoscaler-downs ...
- Netty 如何高效接收网络数据?一文聊透 ByteBuffer 动态自适应扩缩容机制
本系列Netty源码解析文章基于 4.1.56.Final版本,公众号:bin的技术小屋 前文回顾 在前边的系列文章中,我们从内核如何收发网络数据开始以一个C10K的问题作为主线详细从内核角度阐述了网 ...
- Kubernetes 笔记 012 Pod 的自动扩容与缩容
本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. Hi,大家好, ...
- Kubernetes 笔记 11 Pod 扩容与缩容 双十一前后的忙碌
本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. Hi,大家好, ...
- 生产调优4 HDFS-集群扩容及缩容(含服务器间数据均衡)
目录 HDFS-集群扩容及缩容 添加白名单 配置白名单的步骤 二次配置白名单 增加新服务器 需求 环境准备 服役新节点具体步骤 问题1 服务器间数据均衡 问题2 105是怎么关联到集群的 服务器间数据 ...
随机推荐
- three.js尝试(一)模拟演唱会效果
工作闲暇之余,偶然翻到了Three.js的官网,立刻被它酷炫的案例给惊艳到了,当即下定决心要试验摸索一番,于是看demo,尝试,踩坑,解决问题,终于搞定了,一个模拟演唱会场景. 主角围绕一个钢管在舞动 ...
- Vue事件绑定原理
Vue事件绑定原理 Vue中通过v-on或其语法糖@指令来给元素绑定事件并且提供了事件修饰符,基本流程是进行模板编译生成AST,生成render函数后并执行得到VNode,VNode生成真实DOM节点 ...
- SMBMS
SMBMS(Supermarket Billing Management System ) 目录 SMBMS(Supermarket Billing Management System ) 1. 项目 ...
- 藏在Java数组的背后,你可能忽略的知识点
目录 引言 概念 区别于C/C++数组 区别于容器 数组特性 随机访问 Java数组与内存 解惑 数组的本质 Java中的数组是对象吗? Java中数组的类型 Java中数组的继承关系 参考资料 引言 ...
- db2错误代码可能的原因
一. SQL5005C运行cmd未用管理员权限打开 SQLCODE-303 数据类型不兼容 SQLCODE-305 查出的数据有NULL未处理直接写入接收变量 二.应该不会有人直接写sql用这个 ...
- 轻松上手SpringBoot Security + JWT Hello World示例
前言 在本教程中,我们将开发一个Spring Boot应用程序,该应用程序使用JWT身份验证来保护公开的REST API.在此示例中,我们将使用硬编码的用户和密码进行用户身份验证. 在下一个教程中,我 ...
- 本周 GitHub 速览:自动化当道,破密、爬虫各凭本事
作者:HelloGitHub-小鱼干 摘要:安全门外汉,如何在不知道密钥或密码的情况下,破解哈希得到原文,Ciphey 会告诉你当中的密码.说到 auto 智能爬虫会基于上一次的爬虫经历进一步学习以获 ...
- MATLAB 编译器的使用
MATLAB 编译器的使用MATLAB 编译器(相应命令为 mcc)可生成独立应用程序.库.COM 对象.Excel 插件,218 MATLAB 实用教程它根据目标类型生成合适的包装器文件.包装器文件 ...
- 数据库分表分区后的ID生成之雪花生成
转自https://www.cnblogs.com/jajian/p/11101213.html 传统的单体架构的时候,我们基本是单库然后业务单表的结构.每个业务表的ID一般我们都是从1增,通过AUT ...
- 如何使用二维码进行表白-一键制作二维码,gif二维码制作
" 如何使用二维码进行表白-一键制作二维码" 二维码在我们现在生活中随处可见,因为一次突发奇想,那我们可不可以制作一个自己的二维码呢!然后让女朋友去扫描一下----嘿嘿嘿--不行, ...