概述
基础使用
定义:拥有键值对元素的无序集合,键具有唯一性,类型是map[key]value,其中键值都具有相同的数据类型
特点:高性能,低复杂度,无序
初始化
- 默认声明
- 通过内置函数make初始化
- 通过map字面量初始化
func main() {
// 默认声明
var map1 map[string]string // 此时map1是零值nil
//向未初始化的map赋值引起宕机: assign to entry in nil map
// map1["java"] = "大数据"
// make初始化
map2 := make(map[string]string)
map2["golang"] = "web,容器"
map2["python"] = "人工智能"
// 字面量初始化
map3 := map[string]string{
"golang": "容器",
"python": "人工智能",
}
// map是安全的,即使map没有引用,或者key不存在,也不会报错
fmt.Printf(" java 适合做什么:%v n", map1["java"])
fmt.Printf(" golang 适合做什么:%v n", map2["golang"])
fmt.Printf(" python 适合做什么:%v n", map3["python"])
}
out
java 适合做什么:
golang 适合做什么:web,容器
python 适合做什么:人工智能
map增删改查
根据上面初始化的方法,接下来我们对map进行常规操作
注意:map是无序的,如果想要顺序输出,需要对map的键手动排序,再下面最后的demo中会有示例
func main() {
/*
1. 先初始化一个map
2. 循环加入10个元素
3. 删除指定的元素
4. 对指定key的元素值进行重置
5. 读取操作后的map和遍历map
*/
map1 := make(map[int]string)
for i := 1; i <= 10; i++ {
map1[i] = strconv.Itoa(i)
}
fmt.Printf("原始map:%v n", map1)
// 删除key 5的元素
delete(map1, 5)
delete(map1, 20) // 删除一个不存在的key,不会报错,map不进行操作
fmt.Printf("删除后的map:%v n", map1)
// 重置20,40 的值
map1[2] = "20"
map1[4] = "40"
//获取值
fmt.Printf(" map key 5 ,value is %v n", map1[2])
// range 遍历map,map的结果是无序的
for key, value := range map1 {
fmt.Printf(" map key is %v ,value is %v n", key, value)
}
// 手动排序
keys := make([]int, 0, len(map1))
// 把key单独抽取出来,放在切片中
for key := range map1 {
keys = append(keys, key)
}
// 进行数组的排序
sort.Ints(keys)
// 遍历数组就是有序的了
for _, key := range keys {
fmt.Printf(" map key is %v ,value is %v n", key, map1[key])
}
}
out
原始map:map[1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9 10:10]
删除后的map:map[1:1 2:2 3:3 4:4 6:6 7:7 8:8 9:9 10:10]
map key 5 ,value is 20
map key is 7 ,value is 7
map key is 9 ,value is 9
map key is 10 ,value is 10
map key is 3 ,value is 3
map key is 2 ,value is 20
map key is 4 ,value is 40
map key is 6 ,value is 6
map key is 8 ,value is 8
map key is 1 ,value is 1
map key is 1 ,value is 1
map key is 2 ,value is 20
map key is 3 ,value is 3
map key is 4 ,value is 40
map key is 6 ,value is 6
map key is 7 ,value is 7
map key is 8 ,value is 8
map key is 9 ,value is 9
map key is 10 ,value is 10
map的两种取值逻辑
- 直接取value,忽略bool判断
- 获取bool判断和value
对于两套取值方式,使用情况下区别不大,但是对于底层实现还是有区分的,取值的不同对应的不同原理,双参数返回适用于if流程判断
func main() {
map1 := make(map[int]string)
for i := 1; i <= 10; i++ {
map1[i] = strconv.Itoa(i)
}
// fmt.Printf(" 原始map:%v n", map1)
// 1. 直接获取value
value1 := map1[2]
fmt.Printf(" value is %v n", value1)
// 2. 获取判断bool,和value
value2, ok := map1[2]
fmt.Printf(" is has :%v ,value is %v n", ok, value2)
// 双参数返回适用于if流程判断,以下示例
if value2, ok := map1[2]; ok {
fmt.Printf(" map1 has %v n", value2)
}
}
out
value is 2
is has :true ,value is 2
map1 has 2
并发中的map
func main() {
map1 := make(map[int]int)
go func() {
// 死循环,类似while true
// 不停的赋值
for {
map1[0] = 1
}
}()
go func() {
// 不停的读取值
for {
_ = map1[1]
}
}()
// 控制程序不断调,其实这里可以用通道控制,
// 通道的基础,后面会涉及
for {
}
}
out
并发读写后报错了
fatal error: concurrent map read and map write
sync.Map
golang在1.9版本中加入了并发安全的map,和其他语言的区别在于,并发安全的map并不是语言原生支持的,需要借助内置包sync使用
sync.Map 特点
- 存储方式的变更,通过函数的形式
- range遍历时, 需要用回调函数来配合使用,用bool类型标记是否需要继续操作
缺点:
1. 没有提供获取数量的方法,需要在遍历时自行获取
2. 性能不如map
func main() {
var scene sync.Map
go func() {
// 死循环,类似while true
// 不停的赋值
for {
scene.Store("java", "大数据")
}
}()
go func() {
// 不停的读取值
for {
fmt.Println(scene.Load("java"))
}
}()
// 控制程序不断调,其实这里可以用通道控制,
// 通道的基础,后面会涉及
for {
}
}
out
大数据 true
大数据 true
大数据 true
大数据 true
...
sync.Map的遍历
func main() {
var scene sync.Map
// 将键值对保存到sync.Map
scene.Store("java", "大数据")
scene.Store("golang", "容器")
scene.Store("python", "人工智能")
// 从sync.Map中根据键取值
fmt.Println(scene.Load("java"))
// 根据键删除对应的键值对
scene.Delete("golang")
// 遍历所有sync.Map中的键值对
scene.Range(func(k, v interface{}) bool {
fmt.Println("item :", k, v)
return true
})
}
out
大数据 true
item : java 大数据
item : python 人工智能
原理解析(runtime)
存储属性
// A header for a Go map.
type hmap struct {
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
}
- count :Map中元素的数量
- flags : 表示当前Map的状态
- B:map的bucket数组长度的对数
- noverflow 为Map中溢出桶的数量.当溢出桶太多的时候,Map会进行same-size map growth.其实质是为了避免溢出桶过大导致的内存泄露问题
- hash0 代表生成hash的随机数种子
- buckets 指向了当前Map对应的桶的指针
- oldbuckets 是在Map进行扩容的时候存储旧桶的,当所有的旧桶中的数据都已经转移到了新桶,则清空
- nevacuate 在扩容的时候使用。用于标记当前旧桶中小于nevacuate的桶都已经转移到了新桶
- extra存储Map中的溢出桶
上述中buckets是一个指针,指向实际存储的bucket数组的首个字段: 即一个固定长度为8的数组。此字段顺序存储key的哈希值的前8位.
type bmap struct {
tophash [bucketCnt]uint8
}
运行时仅仅通过指针操作即可找到特定位置的元素,所以以上结构并不是golang在运行时,在编译阶段编译器会动态的创建一个新的结构
type bmap struct {
topbits [8]uint8
keys [8]keytype
values [8]valuetype
pad uintptr
overflow uintptr
}
在golang中,map的存储并不是key/value形式的存储,而key和value分开存储是为了更好的在存储过程中进行空间的压缩对齐,如下图(参考gocn)
溢出桶
当指定桶中的数据超过了8个,并不会直接就新开辟一个新桶,而是会将数据放置到溢出桶中,每个桶的最后还存储了overflow 即溢出桶的指针
如果一开始初始化map的数量比较大。则map提前创建好一些溢出桶存储在extra *mapextra 字段
正常操作,数据是很少会跑到溢出桶里面去的,在查找操作时,tophash数组没有制定的可以,还会去遍历溢出桶的数组
负载因子
负载因子 = 哈希表中元素数量 / 桶的数量
- 负载因子越大,说明每个桶的元素越多,效率越低
- golang的负载因子为6.5 ,超过临界值,则map会以2倍大小进行扩容
- 旧桶的数据会首先存到oldbuckets中,后分散的转移到新桶中,数据转移完毕后,旧桶会被清除
Map重建
- 当Map超过了负载因子大小
- 当溢出桶的数量过多
负载因子过大重建
针对上述负载因子,大于6.5后,map重建扩容,扩容后负载因子会降到额定值以下
数据也会从oldbuckets转移到buckets,此过程称为双倍重建
溢出桶过多重建
溢出桶重建不是太好理解,正常来说,数据是很少会跑到溢出桶里面去的,但是在频繁操作插入和删除的情况下,还没有触发负载过大重建,元素已经被删掉了,会导致分配的buckets很多,同时也会创建大量的overflow bucket,这种情况下key 会很分散,查找插入效率非常低,但是此时的负载因子却没达到临界值(每个桶中的元素比较少)
针对这种特殊情况,golang有了第二个重建机制,新开一个bucket,将老 bucket 中的元素移动到新 bucket并重新排列key,原本overflow bucket 中的 key 也移动到新的bucket中来,减少溢出桶的数量,节省内存空间,提高查询和插入效率
Map-delete
与赋值操作类似,会找先到指定的桶,如果存在指定的key,那么就释放掉key与value引用的内存。同时tophash中指定位置会存储emptyOne,代表当前位置是空的,同时在删除操作,会探测到是否当前要删除的元素之后都是空的。如果是,tophash会存储为emptyRest. 这样做的好处是在做查找操作时,遇到emptyRest 可以直接退出,因为后面的元素都是空的
原理解析,参考GoCN
最后
以上就是舒服衬衫为你收集整理的golang基础篇(三)【map应用与原理解析】基础使用原理解析(runtime)的全部内容,希望文章能够帮你解决golang基础篇(三)【map应用与原理解析】基础使用原理解析(runtime)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复