Golang Map 深度剖析:原理、实践与面试要点

news2024/9/21 2:39:49

嘿,小伙伴们!我是 k 哥。今天,咱们来聊聊 Map 。

在 Go 语言这个神奇的世界里,Map 这个有点神秘的数据结构一直都是开发者们特别关注的。

你是不是在用 Map 的时候,对它里面咋工作的感到好奇?是不是碰到复杂操作的时候,特别想弄明白它背后的原理?别着急,今天这篇文章就带你走进 Go 语言 Map 那个神秘的世界,帮你一层一层揭开它的面纱!

从底层的原理,到最佳实践,再到高频面试题的分析,这篇文章会从各个方面满足你的求知心。不管你是刚学的新手,还是经验丰富的老手,相信都能从这里得到宝贵的知识,受到启发。准备好跟我一起开始这场精彩的探索旅程了不?

1 原理

1.1 数据结构
在这里插入图片描述

Map 所涉及的核心数据结构包括两个,即 hmap 和 bmap :

  1. 每当创建一个 map 对象时,在底层都会创建一个 hmap 结构,以用于存储 map 的长度、hash 种子、状态等基础信息。

  2. hmap 指针类型的成员变量 buckets ,指向 bmap 数组。bmap 用于存储键值对。对于相同的键,每次进行 hash 操作后都会定位到 buckets 相同的索引位置进行访问。

  3. 每个 bmap 能够存储 8 个键值对,并且,每个 bmap 设有一个指针,当某个 bmap 存满时,就会申请新的 bmap 进行存储,并与前一个 bmap 构成链表。(通过链地址法解决冲突)

1.1.1 hmap

const (
    // Maximum average load of a bucket that triggers growth is 6.5.
    // Represent as loadFactorNum/loadFactorDen, to allow integer math.
    loadFactorNum = 13
    loadFactorDen = 2
 )

// A header for a Go map.
type hmap struct {
    // Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
    // Make sure this stays in sync with the compiler's definition.
    count     int // # live cells == size of map.  Must be first (used by len() builtin)
    flags     uint8
    B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
    noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
    hash0     uint32 // hash seed

    buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
    oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
    nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

    extra *mapextra // optional fields
}

// mapextra holds fields that are not present on all maps.
type mapextra struct {
    // If both key and elem do not contain pointers and are inline, then we mark bucket
    // type as containing no pointers. This avoids scanning such maps.
    // However, bmap.overflow is a pointer. In order to keep overflow buckets
    // alive, we store pointers to all overflow buckets in hmap.extra.overflow and hmap.extra.oldoverflow.
    // overflow and oldoverflow are only used if key and elem do not contain pointers.
    // overflow contains overflow buckets for hmap.buckets.
    // oldoverflow contains overflow buckets for hmap.oldbuckets.
    // The indirection allows to store a pointer to the slice in hiter.
    overflow    *[]*bmap // 溢出桶数组指针
    oldoverflow *[]*bmap // 迁移过程中,旧溢出桶数组指针

    // nextOverflow holds a pointer to a free overflow bucket.
    nextOverflow *bmap // 还未使用的,提前分配的溢出桶链表
}



每创建一个 map 对象,在 Go 语言底层都会创建 hmap 结构,其成员的含义如下:

  1. count :表示 map 中的数据个数,与 len() 函数相对应。

  2. flags :属于标记字段,用于标记是否正在进行读写操作,以便实现并发读写的检测。

  3. B :代表桶的数量,hash 桶 buckets 的数量为 2^B 个。

  4. noverflow :是溢出桶数量的近似值。

  5. hash0 :为 hash 种子,用于计算 key 的 hash 值。

  6. buckets :是指向由 2^B 个桶所组成的数组的指针。

  7. oldbuckets :指向扩容前的旧 buckets 数组,仅在 map 扩容时发挥作用。

  8. nevacuate :作为计数器,用于标示扩容后搬迁的进度,服务于渐进式迁移。

  9. extra :用于保存溢出桶和未使用溢出桶切片的首地址。

1.1.2 bmap

在这里插入图片描述


const (
    // Maximum number of key/elem pairs a bucket can hold.
    bucketCntBits = 3
    bucketCnt     = 1 << bucketCntBits
)

// A bucket for a Go map.
type bmap struct {
    // tophash generally contains the top byte of the hash value
    // for each key in this bucket. If tophash[0] < minTopHash,
    // tophash[0] is a bucket evacuation state instead.
    tophash [bucketCnt]uint8
    // Followed by bucketCnt keys and then bucketCnt elems.
    
    // Followed by an overflow pointer.
}

bmap 主要用于存储 key-value 对,每个 bmap 能够存储 8 个 key-value 对。bmap 包含 4 个成员变量(尽管在源码中只有一个成员变量 tophash,但实际上在申请内存时,Go 语言会依据 key、value 的具体类型,额外为另外 3 个成员变量分配内存):

  1. tophash 数组,用于存储每个 key hash 之后的高位 hash 值。

  2. key 数组,用于存储 key。

  3. value 数组,用于存储 value。

  4. overflow 溢出指针,指向了下一个 bmap 的地址。

bmap 有个溢出桶指针能指向溢出桶,那 extra 里为啥还得用 *[]*bmap 结构来存所有的溢出桶呢?

这主要是因为 gc 的原因。在早前的 Go 语言版本里,gc 会把 buckets 里的所有对象都扫一遍。要是 map 存的 key-value 对特别多,gc 能花上几百毫秒到好几秒,这就会让一些用 Go 语言开发的服务,接口超时超得厉害。

为了处理这个情况,Go 语言官方改了 map 的实现。要是 map 满足下面这两个条件,那 bmap 里除了 overflow ,就没别的指针了,而且会被 gc 标记成不用扫描:

  • key 和 value 不是指针类型,里面也不含指针(int 类型行,string 类型不行,因为 string 底层的数据结构里有指针)。

  • key 和 value 的大小得小于 128 字节。

但是 bmap.overflow 是指针类型,如果 gc 不扫 buckets ,溢出桶就可能被 gc 错误地回收掉。为了不让这种情况发生,就在 extra 里用 *[]*bmap 来存溢出桶,这样 gc 就能通过 []*bmap 扫到溢出桶(不用扫桶里面的东西),也就不会把溢出桶错误回收了。

1.2 插入或更新

1.2.1 2种异常情况处理

// Like mapaccess, but allocates a slot for the key if it is not present in the map.
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
    // 为nil则panic
    if h == nil {
        panic(plainError("assignment to entry in nil map"))
    }
    // 并发读写会抛异常,且不能被defer捕获
    if h.flags&hashWriting != 0 {
        throw("concurrent map writes")
    }
    // 计算key对应的hash值
    hash := t.hasher(key, uintptr(h.hash0))

    // 设置正在写标记
    h.flags ^= hashWriting
    // 初始化,但是桶为空的,会自动创建桶数组,读写不会panic
    if h.buckets == nil {
      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2048985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java流程控制06:嵌套for循环

本节教学视频链接&#xff1a;https://www.bilibili.com/video/BV12J41137hu?p41&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5https://www.bilibili.com/video/BV12J41137hu?p41&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 在Java中&#xff0c;‌嵌套for循环是指将…

使用三菱PLC源码进行PLC读取写入操作

安装 MX Component 。 我的安装地址在&#xff1a; 打开 utl 文件夹下的 Communication Settings Utility 执行。 配置PLC 添加当前需要配置的PLC 注意 logical station Namber 就是程序里需要对接的逻辑站点编号 5.配置选择对应的COM操作选择对应的cpu型型号&#xff0c;…

Ah That‘s Hawt

目录 一、题目 二、思路 三、payload 3.1 方案一 3.1 方案二 四、思考与总结 一、题目 <!-- Challenge --> <h2 id"will"></h2> <script>smith (new URL(location).searchParams.get(markassbrownlee) || "Ah Thats Hawt")sm…

甄选系列“论软件开发过程RUP及其应用”,软考高级论文,系统架构设计师论文

论文真题 RUP(Rational Unified Process)是IBM公司的一款软件开发过程产品,它提出了一整套以UML为基础的开发准则,用以指导软件开发人员以UML为基础进行软件开发。RUP汲取了各种面向对象分析与设计方法的精华,提供了一个普遍的软件过程框架,可以适应不同的软件系统、应用…

ant -design 框架以及具体调试

1.介绍 Ant-Design-Vue 是一个基于 Ant Design 设计体系的 Vue 实现。Ant Design 是由阿里巴巴开源的一个企业级 UI 设计语言&#xff0c;旨在提升用户体验和开发效率。Ant-Design-Vue 将 Ant Design 的设计理念和组件库带入了 Vue 生态系统&#xff0c;使得开发者能够在 Vue …

RabbitMQ集群 - 普通集群搭建、宕机情况

文章目录 RabbitMQ 普通集群概述集群搭建数据准备启动容器宕机情况 RabbitMQ 普通集群 概述 1&#xff09;普通模式中所有节点没有主从之分&#xff0c;所有节点的元数据&#xff08;交换机、队列、绑定等&#xff09;都是一致的. 例如只要有任意一个节点上面 新增交换机&…

迈出Python自动化测试的第一步

一、思考❓❔ 1.什么是性能自动化测试? 性能 系统负载能力超负荷运行下的稳定性系统瓶颈 自动化测试 使用程序代替手工提升测试效率 性能自动化 使用代码模拟大批量用户让用户并发请求多页面多用户并发请求采集参数&#xff0c;统计系统负载能力生成报告 2.Python中的性能自…

C++竞赛初阶L1-12-第五单元-while(27~28课)531: T456440 含 k 个 3 的数

题目内容 输入两个正整数 m 和 k&#xff0c;其中 1<m≤1015&#xff0c;1<k≤15 &#xff0c;判断 m 是否恰好含有 k 个 3&#xff0c;如果满足条件&#xff0c;则输出 YES&#xff0c;否则&#xff0c;输出 NO。 输入格式 输入一行&#xff0c;为两个整数 m,k&#x…

【js面试题】js原型,原型链?有什么特点

在 JavaScript 中&#xff0c;原型&#xff08;Prototype&#xff09;和原型链&#xff08;Prototype Chain&#xff09;是实现继承和共享属性与方法的核心机制。理解它们对于深入掌握 JavaScript 的对象模型非常重要。 原型&#xff08;Prototype&#xff09; 每个 JavaScri…

24年上半年天融信营收缩减1.8亿,亏损2.06亿

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330#rd 《网安面试指南》http://mp.weixin.qq.com/s?…

建筑三剑客:平面、剖面与立面图解析

平面图、剖面图与立面图是建筑学中不可或缺的工具&#xff0c;它们共同构成了建筑设计的基础。平面图展示了建筑物的顶部视图&#xff0c;详细标出了房间布局和空间关系。剖面图则揭示了建筑的内部结构&#xff0c;包括楼层分布和垂直交通。而立面图呈现了建筑的外观&#xff0…

【学习笔记】Matlab和python双语言的学习(一元线性回归)

文章目录 前言一、一元线性回归回归分析的一般步骤一元线性回归的基本形式回归方程参数的最小二乘法估计对回归方程的各种检验估计标准误差的计算回归直线的拟合优度判定系数显著性检验 二、示例三、代码实现----Matlab四、代码实现----python回归系数的置信区间公式残差的置信…

入门MySQL数据库

目录 一、MySQL的安装&#xff08;以5.7版本为例&#xff09; 1. 一路默认安装即可&#xff0c;注意root密码。 2.配置环境变量 3.登录数据库 二、指令 1.数据库 2.数据表 3.约束 4.增删改查 1>查 2>增 3>改 4>删 5.数据库用户 6.外键 1>创建添加外…

实验5:数码管实验,51单片机

8个数码管 LED1-LED8分别有P22,P23,P24的A,B,C控制 C B A 000 0-1 001 1-2 010 2-3 011 3-4 101 4-5 110 6-7 111 7-8 共阴极数码管,八段 0-F编码 硬件图 对应P0口 main.c #include<reg52.h>typedef unsigned int u16; typedef unsigned char u8;#d…

RPA在政务领域的发展前景

随着信息技术的迅猛发展&#xff0c;政务领域也在不断探索创新&#xff0c;以提升政府服务的质量和效率。RPA作为一种自动化技术&#xff0c;打破了传统政务服务人工操作的局限&#xff0c;协助基层人员更高效准确地完成录入、审查、校对和数据汇总等各项繁琐的工作&#xff0c…

第1节 安装Flask

我们以Thonny4为例&#xff1a; flask是第一个第三方库。与其他模块一样&#xff0c;安装时可以直接使用python的pip命令实现。 一、找到你的安装目录 这是我的安装目录&#xff1a; D:\thonney4\scripts 二、执行pip pip install Flask

LabVIEW VI 多语言动态加载与运行的实现

在多语言应用程序开发中&#xff0c;确保用户界面能够根据用户的语言偏好动态切换是一个关键需求。本文通过分析一个LabVIEW程序框图&#xff0c;详细说明了如何使用LabVIEW中的属性节点和调用节点来实现VI&#xff08;虚拟仪器&#xff09;界面语言的动态加载与运行。此程序允…

人像修复-DB双曲线

相对于中性灰图层修复&#xff0c;不容易掉色&#xff0c;光影过渡更均匀&#xff0c;适合大范围调整光影&#xff0c;而中性灰适合更调整细节主要用于修饰光影均匀过渡&#xff0c;先大范围修饰整体&#xff0c;再局部细节修饰 建立明度观察层&#xff08;渐变映射曲线&#x…

Xilinx系ZYNQ学习笔记(二)ZYNQ入门

系列文章目录 文章目录 系列文章目录前言简单介绍简称 xc7z020型号FPGAZYNQ实操通用IO点亮LED灯硬件逻辑基础 前言 简单入门一下ZYNQ是何种架构&#xff0c;如何编程&#xff0c;至于深入了解应该要分开深入学习Linux和FPGA 简单介绍 其基本架构都是在同一个硅片上集成 FPGA …

『Android』如何配置 Jetpack-Compose 环境

记录配置Jetpack Compose环境的一些坑~ 直接创建kotlin项目或创建java项目后再配置均可 根目录 build.gradle 配置kotlin环境构建脚本 buildscript {ext.kotlin_version 1.4.32dependencies {classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version&q…