口述Map

1、Go语言Map的底层实现原理是怎样的?

Go语言中Map底层其实就是一个哈希表 ,也就是一个叫hmap的结构体。所以Map在运行时的表现为,一个指向hmap结构体的指针。

hmap这个结构体,内部有几个挺重要的字段,分别是Bflagscountbucketsoldbuckets...

其中bucketsoldbuckets是一个指针,指向对应的数组的地址。这些数组中放着的是,bmap的数据结构。

bmap也是一个结构体,其内可以存8个key-value的键值对 ,与8个tophash(高八位),还附带着一个指向下一个溢出桶overflow-bmap)的位置。

注:

存放 Key 值和 Value 值的时候是分开存放的。先存放 Key 值,然后紧接着把 8 个 Key 值存完之后,才会存放 Value 值。这样是为了内存对齐。方便快速存取,与避免空间浪费。

2、Go语言Map的遍历是有序的还是无序的?

Go语言中Map的遍历时无序的 ,他会从一个随机的桶 中的,随机出来的 槽位(slot)开始,向圆圈一样,遍历一整圈。因为每次都是随机的,所以无序。

3、Go语言Map的遍历为什么要设计成无序的?

这个其实是设计者,故意的设计。就是害怕某些人钻漏洞,把map当成排序来用。因为每次遍历桶之前,他都可能刚经历过等量或者增量扩容 ,导致原本的存储位置,被打乱,比如有的key-value还傻傻的在原地,而另一些key-value又被规则重新分配到了其他位置。

所以每次遍历Map,Go语言都会通过引入随机数,打乱开始遍历Map的起点。

4、Map如何实现顺序读取?

因为Map不支持顺序读取 ,所以可以先把Map内的键值对,重新取出来,另放到一个切片 中,然后sort排序一下。

5、Go语言的Map是否是并发安全的?

Map当然不是并发安全的

如果不同协程/线程一起读,若只是只读 还是没有问题的。

但,你还记得hmap内部有一个flags的标记位吗?他是int类型的。你对他新增的或者删除的时候,都会将标记位先置为4

这时你去读。Go语言就会直接给你来个Painc

6、Map的Key一定要是可比较的吗?为什么?

必须要可比较

因为要把key-value,存进map里面的时候,会先对key进行哈希计算 ,得出一个哈希值

然后拿着这个哈希值,取低B位 ,找到他所在的bmap桶。

然后再取这个哈希值的高8位 ,去找它他应该到的槽位(slot

而这个所谓的 B ,则是hmap哈希表结构体中的一个uint8类型的字段。代表整个哈希表一共有,2B次方的桶。比如B=3,则有23次方=8个桶。

注:旧版哈希冲突是这样解决的:

他先在主桶内的8个槽位 寻找空位,如果8个槽位满了,则会通过 overflow bucket ,去寻找接下来的溢出桶,如果没有就新建...往复循环。

7、Go语言Map的扩容时机是怎样的?

分为两种扩容,一种是增量扩容,一种是等量扩容
等量扩容

  • 官方上说,若负载因子大于6.5,则容量翻倍。(B+1了)

增量扩容

  • B小于15时,总溢出桶的个数大于2B次方,则等量扩容。
  • B大于等于15时,总溢出桶的个数一旦大于215次方,就等量扩容。

8、Go语言Map的扩容过程是怎样的?

因为一次性搬迁完的话,会导致当次的写入操作明显卡顿。所以Go采用了渐进式搬迁

就拿翻倍扩容 举例子,他第一次先不搬完。而是每次 插入 / 删除 / 修改 的时候,顺带着搬一点。保证了平滑性。

9、可以对Map的元素取地址吗?

go 复制代码
如:fmt.Println(&map[3])

当然不可以,因为某个key的具体位置,随时会变动,所以这在Go语言内是不允许的。

10、Map 中删除一个 key,它的内存会释放么?

删除map中一个key后,该entry会被清除;

若其关联对象不再可达,后续可被GC回收。

只是mapbucket空间通常不会因为一次delete立刻释放。他是要等GC的。

11、Map可以边遍历边删除吗

在同一个 goroutine 里是允许的

Go 语言规范明确允许在 range 遍历 map 时删除元素:如果某个尚未遍历到的 entry 被删掉了,它之后就不会被产出;

如果遍历过程中新增元素,则"可能被遍历到,也可能不会"。
但普通 map 不是并发安全的

如果一个 goroutine 在遍历或读取,另一个 goroutine 同时写入或删除,运行时经常会直接报错。

补充:

自定义快排

go 复制代码
package main

import (
	"fmt"
	"slices"
)

type Person struct {
	Name string
	Age int
}

func main() {
	ps := []Person{
		{"Tom", 23},
		{"Alice", 18},
		{"Bob", 20},
	}
	nums := []int{5, 2, 9, 1, 3}
	// 正序
	slices.Sort(nums)
	// 反序
	slices.Reverse(nums)
	// 自定义
	slices.SortFunc(ps, func(a, b Person) int {
		return a.Age - b.Age // 按年龄升序
	})

	fmt.Println(ps)
}
相关推荐
Evand J5 分钟前
【MATLAB例程,车联网8】MPC网联车辆节能跟驰控制:速度跟踪、间距约束与能耗优化。附MATLAB完整代码的下载链接
开发语言·matlab·车联网·调度·mpc·节能·车辆控制
ttwuai22 分钟前
Go 后台接入 SSO 后菜单正常但接口 403,怎么排查权限链路?
开发语言·后端·golang
Canmag 兴隆磁性33 分钟前
C 系列充磁机
c语言·开发语言·学习·永磁材料·充磁·退磁
benchmark_cc1 小时前
批量获取量化数据时,如何设置合理的超时和重试机制?——QuantDash 高性能实战指南
开发语言·人工智能·python·pandas·量化·quantdash
2601_966949651 小时前
使用 Pandas 读取批量数据时如何避免内存溢出?QuantDash 量化数据工程师避坑指南
开发语言·python·pandas·tushare·akshare·quantdash
郑州光合科技余经理1 小时前
海外版多语言团购系统架构:主数据互通与核销边界
java·开发语言·前端·后端·系统架构·php·ai编程
ttwuai1 小时前
Go 后台图片上传到对象存储后,预览 403/404 怎么排查?
开发语言·golang
绿浪19842 小时前
c# 结构体 能不能直接封送检测
开发语言·c#
触底反弹2 小时前
🔥 从「为什么」到「怎么用」:TypeScript 类型约束与泛型完全指南
后端·面试·typescript
李剑一2 小时前
有点干,前端架构基础之:Web Worker到底是什么?它和Java中的线程是一个道理吗?
前端·面试·架构