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的自动扩容与手动缩容的更多相关文章

  1. 023.掌握Pod-Pod扩容和缩容

    一 Pod的扩容和缩容 Kubernetes对Pod的扩缩容操作提供了手动和自动两种模式,手动模式通过执行kubectl scale命令或通过RESTful API对一个Deployment/RC进行 ...

  2. Redis Cluster 自动化安装,扩容和缩容

    Redis Cluster 自动化安装,扩容和缩容 之前写过一篇基于python的redis集群自动化安装的实现,基于纯命令的集群实现还是相当繁琐的,因此官方提供了redis-trib.rb这个工具虽 ...

  3. k8s Pod 扩容和缩容

    在生产环境下,在面临服务需要扩容的场景时,可以使用Deployment/RC的Scale机制来实现.Kubernetes支持对Pod的手动扩容和自动扩容. 手动扩容缩容 通过执行扩容命令,对某个dep ...

  4. 数据结构 5 哈希表/HashMap 、自动扩容、多线程会出现的问题

    上一节,我们已经介绍了最重要的B树以及B+树,使用的情况以及区别的内容.当然,本节课,我们将学习重要的一个数据结构.哈希表 哈希表 哈希也常被称作是散列表,为什么要这么称呼呢,散列.散列.其元素分布较 ...

  5. 如何根据不同业务场景调节 HPA 扩缩容灵敏度

    背景 在 K8s 1.18 之前,HPA 扩容是无法调整灵敏度的: 对于缩容,由 kube-controller-manager 的 --horizontal-pod-autoscaler-downs ...

  6. Netty 如何高效接收网络数据?一文聊透 ByteBuffer 动态自适应扩缩容机制

    本系列Netty源码解析文章基于 4.1.56.Final版本,公众号:bin的技术小屋 前文回顾 在前边的系列文章中,我们从内核如何收发网络数据开始以一个C10K的问题作为主线详细从内核角度阐述了网 ...

  7. Kubernetes 笔记 012 Pod 的自动扩容与缩容

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. Hi,大家好, ...

  8. Kubernetes 笔记 11 Pod 扩容与缩容 双十一前后的忙碌

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. Hi,大家好, ...

  9. 生产调优4 HDFS-集群扩容及缩容(含服务器间数据均衡)

    目录 HDFS-集群扩容及缩容 添加白名单 配置白名单的步骤 二次配置白名单 增加新服务器 需求 环境准备 服役新节点具体步骤 问题1 服务器间数据均衡 问题2 105是怎么关联到集群的 服务器间数据 ...

随机推荐

  1. windows远程连接老是出问题?如何使用Radmin进行云服务器的远程连接与文件传输?

    (windows远程连接老是出错怎么办?云服务器远程连接一直有问题怎么办?如何用对多台windows电脑远程连接怎么办? 最近发现win的mstsc不好用,偶然想起Radmin这款老牌软件,利用Rad ...

  2. springboot2.x基础教程:Swagger详解给你的接口加上文档说明

    相信无论是前端还是后端开发,都或多或少地被接口文档折磨过.前端经常抱怨后端给的接口文档与实际情况不一致.后端又觉得编写及维护接口文档会耗费不少精力,经常来不及更新.其实无论是前端调用后端,还是后端调用 ...

  3. CSS中的包含块

    1.初始包含块,浏览器viewport大小 2.非根元素,position:relative/static,包含块为最近的块级框,表格单元或行内祖先框的内容区 3.非根元素,position:abso ...

  4. 微服务架构组件梳理之Netflix停更之后该何去何从

    自2018年底,Netflix陆续宣布Eureka.Hystrix等框架进入维护状态,不再进行新功能的开发. 恰逢最近我打算对公司的办公项目进行微服务架构升级,所以恶补了一番微服务相关知识,在这里进行 ...

  5. 干货:用好这13款VSCode插件,工作效率提升10倍

    文章每周持续更新,原创不易,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) 大家好我是lemon, 马上进入我们今天的主题吧. 又 ...

  6. Nginx(二): worker 进程处理逻辑-流程框架

    Nginx 启动起来之后,会有几个进程运行:1. master 进程接收用户命令并做出响应; 2. worker 进程负责处理各网络事件,并同时接收来自master的处理协调命令: master 主要 ...

  7. db2官方文档

    开局贴链接.这个东西是真坑,下载竟然需要账号... (我就做一下记录,别喷我)

  8. Django 仿ajax传递数据(Django十)

    之前用form表单传递数据,没有遇到任何问题 具体见:https://blog.csdn.net/qq_38175040/article/details/104867747 然后现在我想用ajax传递 ...

  9. list列表(也叫数组),以及常用的一些方法

    列表的表达: 元祖tuple,元祖是不可被修改的列表 1.列表的增,list.append(元素).或list.insert(index,元素) 2.列表的删,list.pop(可指定index也可不 ...

  10. Linux下rm操作误删恢复

    1.查看被误删的分区 df /home/Java/...      一直到刚刚被误删的文件的路径下 2.在debugfs打开分区 open /dev/ssl       最好这个分区可能不一样,根据上 ...