Jquery中文网 www.jquerycn.cn
Jquery中文网 >  后端编程  >  Go语言  >  正文 golang slice map 的一些常见坑

golang slice map 的一些常见坑

发布时间:2021-04-09   编辑:www.jquerycn.cn
jquery中文网为您提供golang slice map 的一些常见坑等资源,欢迎您收藏本站,我们将为您提供最新的golang slice map 的一些常见坑资源

1,常看到有博客或者书说道,golang内部有引用类型,这里说明一哈,golang里面所有的类型都是值类型,之所以有些用起来像引用,是在于该类型内部是用指针实现的,但是其本质就是包含指针的结构体。

我们常常用错的类型就是Slice.以下是slice这个类型的实现:

type slice struct {
	array unsafe.Pointer  // 内部是分配一个连续的内存块,这个指针就指向这个内存的首地址
	len   int // 数组长度
	cap   int // 数组容量
}
当不断的向slice添加数据,len就不断变大,当len > cap的时候,内部开始扩容,重新分配一块内存,大致的扩容策略是cap * 2,然后将老地址的数据copy到新地址。

由于以上的特性,当有一个slice a, 将a 复制给b, 这个时候,a, b指向同一个底层数组,当对a不断的进行添加操作,当a进行扩容后,a,b便开始指向不同的底层数组。所有在操作slice的时候要格外注意。

我们常常的错误使用方式有以下几点:

	// 错误使用方式1:
	data := make(map[string][]int32)
	item0 := make([]int32, 0)
	item0 = append(item0, 1)
	item0 = append(item0, 2)
	data["item0"] = item0

	// 获取到item0, 并向其中添加两项
	temp := data["item0"]
	temp = append(temp, 3)
	temp = append(temp, 4)

	// 输出结果是:temp len is 4, data["item0"] len is 2
	// 注意,这里返回的temp 是一个副本,虽然内部指向同一个底层数组,但是仍然是不同的slice值
	fmt.Printf("temp len is %v, data[\"item0\"] len is %v\n", len(temp), len(data["item0"]))

	// 错误使用方式2:
	item1 := make([]int32, 0, 2)
	item1 = append(item1, 1)
	item1 = append(item1, 2)

	// 注意,这里的赋值,只是简单的复制了一个slice的副本,item1, item2拥有相同的cap, len,
	// 以及指向同一个底层数组的指针,在没有扩容前,因为共享同一个底层数组,就需要格外小心
	item2 := item1

	// item1扩容前,item1, item2的修改会相互影响
	// 结果:item1[0] is 6, item2[0] is 6
	item2[0] = 6
	fmt.Printf("item1[0] is %v, item2[0] is %v\n", item1[0], item2[0])

	// item2扩容后,item1, item2分别拥有不同的底层数组
	// 结果:item1[0] is 6, item2[0] is 4
	item2 = append(item2, 3)
	item2[0] = 4
	fmt.Printf("item1[0] is %v, item2[0] is %v\n", item1[0], item2[0])

2,然后说一说golang里面的map,golang里面的是通过hashtable来实现的,具体方式就是通过拉链法(数组 链表)来实现的,这里对比一哈c 的map,c 里面的map 是通过红黑树来实现的,所以二者在遍历的时候做删除操作,golang的是可以直接操作的,因为内部实现是哈希映射,删除并不影响其他项,而c 中的map删除,由于是红黑树,删除任意一项,都会打乱迭代指针,所以不能直接删除。同时,golang里面的key是无序的,即使你顺序添加,遍历的时候也是无序。

golang里面的map,当通过key获取到value时,这个value是不可寻址的,因为map 会进行动态扩容,当进行扩展后,map的value就会进行内存迁移,其地址发生变化,所以无法对这个value进行寻址。

所以就产生了如下错误:

	type Entity struct {
		Desc string
	}

	entityMap := make(map[string]Entity, 0)
	entityMap["Cup"] = Entity{Desc: "This is a Cup"}

	// 这里会编译报错,因为entityMap["Cup"]是不可寻址的,所以不能直接访问内部变量
	entityMap["Cup"].Desc = "This is a special Cup"

3,看一看map 的内部实现,map 内部只要是两个数据结构,hmap和bmap,hmap里面最主要的是一个buckets指针,指向一个bucket数组, 而每个bucket(bmap)分别放着tophash数组, key/value键值对(key1, key2, key3...|value1,value2,value3...),next bucket指针。存储键值对,将key, value分别放在一起,是为了内存对齐,减少内存消耗。

对于扩容,当装载因子过小时,会触发扩容,一般情况是以2倍的方式分配一个新的bucket数组,然后将oldbucket的数据复制到newbucket, 但是不是立即复制,而是当再次访问oldbucket里面的数据时,才将对应的bucket复制到newbuckets的桶里,copy完成后,oldbucket被gc回收。

当对map 进行delete操作时,并不会删除对于的内存空间,只是将bucket里面的对应项清空。

4,对map而言,make返回的是指针,所有a:=b,其实就是创建指针副本,所以map在进行赋值,其实是hmap指针的复制,所以赋值后的变量共享同一个hamp,所以当任意一个map变量扩容,其他的变量都相应发生变化,表现特别像引用

// makemap implements Go map creation for make(map[k]v, hint).
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h != nil, the map can be created directly in h.
// If h.buckets != nil, bucket pointed to can be used as the first bucket.
func makemap(t *maptype, hint int, h *hmap) *hmap {
}

所以我们在使用的时候要注意:

	// 返回一个hmap 指针, 
	a := make(map[int]int, 2)
	a[1] = 1
	a[2] = 2
	// 创建一个*hamp副本, a, b共享一个hmap实体
	b := a

对slice而言,make返回的是对象,所以a:=b,就是创建一个对象副本。ab是不同的slice实体,刚开始,ab指向同一个底层数组,当其中一个变量扩容后,ab开始指向不同的底层数组

func makeslice(et *_type, len, cap int) slice {
	// NOTE: The len > maxElements check here is not strictly necessary,
	// but it produces a 'len out of range' error instead of a 'cap out of range' error
	// when someone does make([]T, bignumber). 'cap out of range' is true too,
	// but since the cap is only being supplied implicitly, saying len is clearer.
	// See issue 4085.
}
	//对slice而言,make返回的是对象,所以a:=b,就是创建一个对象副本
	a := make([]int, 2)
	a = append(e, 1)
	a = append(e, 2)

	// 创建一个slice副本, a, b是两个不同的slice实体,内部指针指向同一个底层数组
	b := a

 

到此这篇关于“golang slice map 的一些常见坑”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
golang slice map 的一些常见坑
Golang Slice和map的坑
golang 解析struct为map_Golang 的引用类型底层实现
golang 面试
Golang map类型 复杂map类型
Golang 中使用 Slice 索引 Map 替代 Map 获得性能提升
golang map key 正则表达_Golang中的Map
Golang的map并发安全
Go基础编程:Map
Golang从入门到放弃200618--Map(1)Map的初始化和基本操作

[关闭]