2.关于go的slice-map-string数据结构
2026/2/25大约 4 分钟
2.关于go的slice-map-string数据结构
数据结构1:slice的3个成员
type slice struct {
array unsafe.Pointer
len int
cap int
}情况1【不推荐】使用var tmp []int

情况2【推荐】使用make
var ints []int = make([]int, 2, 5)
情况3【不推荐】使用new
ps := new([]string)
那谁来给他分配底层数组呢?
- append函数!
*ps = append(*ps, "eggo")数据结构2:string的2个成员
- https://github.com/golang/go/blob/go1.22.0/src/runtime/string.go
字符串在Go中是一个结构体,包含两部分:
一个指向实际字符串数据的指针和一个表示字符串长度的整数。
字符串是无法被修改内容的变量类型
type stringStruct struct {
str unsafe.Pointer
len int
}数据结构3:map的2个结构体
Go的map的源码实现是典型的哈希碰撞解决方案!
数据结构与算法说了:
解决哈希冲突的方法一般有:开放定址法、链地址法(拉链法)
Go 的 map 本质上是一个 哈希表 + 桶(bucket)结构,而哈希碰撞是必然会发生的,Go 的解决方式是 链式+溢出桶 的结合(我认为就是拉链法的方法,但是为了加快更快的速度,使用了桶的概念,使得比教材上的拉链法更快,因为go的桶里面能放好几个key、value对】
Go运行时使用一张哈希表来实现抽象的map类型(也就是hmap)
- 运行时实现了map操作的所有功能,包括查找、插入、删除、遍历等。
- 在编译阶段,Go编译器会将语法层面的map操作重写成运行时对应的函数调用
结构体1:hmap(hash map)
type hmap struct {
count int //当前map中的元素个数;对map类型变量运用len内置函数时,len函数返回的就是count这个值。
flags uint8 //当前map的状态
B uint8 //B的值是bucket数量的以2为底的对数,即2^B = bucket数量
noverflow uint16 //map中溢出桶的数
hash0 uint32 //是哈希的种子,它能为哈希函数的结果引入随机性,这个值在创建哈希表时确定,并在调用哈希函数时作为参数传入
// 指向当前map对应的桶的指针【比如bmap的数组】
buckets unsafe.Pointer
//在map扩容阶段指向前一个bucket数组【bmap数组】的指针。
oldbuckets unsafe.Pointer
nevacuate uintptr //在map扩容阶段充当扩容进度计数器。所有下标号小于nevacuate的bucket都已经完成了数据排空和迁移操作。
//存储map中的溢出桶
extra *mapextra
}
type mapextra struct {
overflow *[]*bmap
oldoverflow *[]*bmap
nextOverflow *bmap
}结构体2:bmap(bucket map)
map 使用的桶长什么样子?
一个桶里可以放8个键值对,但是为了让内存排列更加紧凑,八个key放一起,八个value放一起
在八个key的前面则是八个tophash【这是啥?】,每个tophash 都是对应哈希值的高 8 位
附录1、针对slice和map的建议
- 建议使用make初始化或者使用
{}去初始化!
| 【我最常用】 | ||
|---|---|---|
| 用法 | make(map[K]V) | map[K]V{} |
| 是否创建空 map | ✅ | ✅ |
| 能否直接添加键值 | ✅ | ✅ |
| 语义 | 显式构建一个空的、准备写入的 map | 创建一个字面量空 map |
| 是否能初始化带值 | ❌ 不行(只能空) | ✅ 可以初始化 key/value |
| 是否能用于接口返回优化 | ✅ 更清晰 | ✅ 也可以 |
| 是否能指定容量(slice) | ✅ 可以:make([]int, 0, 100)指定长度为0,容量为100 | ❌ 不可以 |
| 性能差异 | 几乎没有 | 几乎没有 |
附录2、关于go的slice的append方法
语法1给slice依次加入n个元素
slice = append(slice,elem1,elem2)语法2合并另一个slice
// 注意,合并另一个slice后边要加上3个点哈!
slice = append(slice,anotherSlice...)举例,力扣的这个:144. 二叉树的前序遍历
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func preorderTraversal(root *TreeNode) []int {
if root == nil {
return []int{}
}
ret := []int{}
ret = append(ret,root.Val)
LeftVal := preorderTraversal(root.Left)
if 0 != len(LeftVal){
// 合并另一个slice
ret = append(ret,LeftVal...)
}
RightVal := preorderTraversal(root.Right)
if 0 != len(RightVal){
ret = append(ret,RightVal...)
}
return ret
}附录3、关于make函数在不同场景的作用
- 来自官方文档:https://pkg.go.dev/builtin@go1.26.0
make用来创建并初始化下面三种类型(只能是这三种): - slice(切片)
- map(字典)
- chan(通道)
slice
make([]int, length, capacity)- 第二个参数:长度(length)
- 第三个参数:容量(capacity,可选)
- 如果不写 capacity,默认等于 length
map
make(map[string]int, size)- size 表示预估能放多少元素(可选)
- 不是最大容量,只是初始分配空间
chan
make(chan int, bufferSize)- bufferSize = 缓冲区大小(可选)
- 如果为 0 或不写,就是无缓冲通道