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. WinMTR 网络测试工具-九五小庞

    WinMTR(建议优先使用) 百度下载工具 链接:https://pan.baidu.com/s/19ArKSTA2amsa4p6vHegDIQ 提取码:cy4y WinMTR是mtr工具在Windo ...

  2. pwnable.kr之leg

    查看原题代码: #include <stdio.h> #include <fcntl.h> int key1(){ asm("mov r3, pc\n"); ...

  3. 给编程小白的java JDK安装教程

    对刚开始学习编程的新手朋友们来说,安装开发环境可谓是一件头疼的事了,为了不让刚准备入门java的朋友还没写出自己的Hello World就被安装开发环境劝退,所以本篇文章诞生了. 下载JDK 打开浏览 ...

  4. conda和pip重新配置源

    conda设置源之后出现了问题,报错condaHTTPError: 之前按照网上的一些教程设置了清华源之后,过了一段时间,今天来装新的库时报了以上错误,特此记录一下. conda 源重新设置 重新去清 ...

  5. robotframework安装与运行(ride.py1.7.4.2命令报错)(win10+python3.8.1)

    首先,robotframework必须在python的环境下运行 所以你的电脑里必须有python3.x的环境先. 上古时期的robotframework只支持python2,不过python2都已经 ...

  6. docker中重启某个服务命令

    docker ps------查看正在运行的cotainners docker ps -a --------查看所有的containners docker restart 容器id docker lo ...

  7. [程序员代码面试指南]递归和动态规划-最长公共子串问题(DP,LCST)

    问题描述 如题. 例:输入两个字符串 str1="1AB234",str2="1234EF" ,应输出最长公共子串"234". 解题思路 状 ...

  8. BFC与HasLayout的理解

    1.(Block Formatting Contexts)BFC 定义 BFC(Block formatting context)直译为"块级格式化上下文".它是一个独立的渲染区域 ...

  9. MFC DLL中如何响应PreTranslateMessage消息

    最近项目中使用到MFC,由于工程是DLL的,在使用ToolTip时碰到非模态对话框无法显示的问题.查了一番资料,发现原因是由于:虽然MFC Regular DLL派生了CWinApp类,并有一个the ...

  10. Swiper 在IE9 及其他浏览器使用

    Swiper 在IE9 及其他浏览器使用 前言 昨天遇到一个问题,swiper 使用版本是3.4.2 除了Ie9浏览器外其他浏览器都正常,IE9 无法轮播,执行控制台报错源码问题.没办法,只能降级兼容 ...