golang中channel,slice,map的学习记录

1.channel

1.1管道的基本结构

在这里插入图片描述

1.环形队列

缓冲区是一个环形队列,长度是在创建chan时指定的,其中的qcount表示队列的剩余的元素的个数,dataqsiz队列的长度,buf是指向环形队列的指针。
使用数组实现队列是比较常见的方式,sendx和recvx是分别表示队尾和队首

2 .等待队列

从管道读取数据时,如果管道缓冲区没有数据或没有缓冲区,则当前协程会被阻塞,并被放入recvq中,同样的sendq是写数据时,缓冲区已满或者无缓冲区,当前协程会被放入sendq

3.互斥锁

一个管道同时只能被一个协程读写

1.2常见用法

1.单向管道

主要是通过形参的方式进行限定
func readChan(ch <- chan int) //在函数内部是只能读取数据
func writeChan(ch chan <- int)//在函数内部是只写数据chan

2.select

使用select可以监控多个管道,当其中某一个管道可操作时就触发相应的case分支

3.for -range

for-range可以持续的从管道中读取数据,好像遍历一个数组一样,当管道中没有数据时会阻塞当前协程。

for e := range chanName{
	fmt.Printf("from chan:%d\n",e)
}

###1.3常见的问题
读写nil管道会阻塞,关闭的管道仍然可以读取数据,向关闭的管道中写入数据时会触发panic.

2.slice

切片,动态数组。在Go语言中传递数组是纯粹的值拷贝,对于元素类型长度较大或元素个数较多的数组,如果直接以数组类型参数传递到函数中会有不小的性能损耗。这时很多人会使用数组指针类型来定义函数参数,然后将数组地址传进函数,这样做的确可以避免性能损耗,但这是C语言的惯用法,在Go语言中,更地道的方式是使用切片。切片类似于数组的指针,它指向底层数组。

2.1数据结构

type slice struct{
	array unsafe.Pointer
	len int 
	cap int 
}

array指向底层的数组,len表示切片的长度,cap表示底层数组的容量

2.2切片的操作

1.使用make创建

slice := make([]int, 5, 10)//同时指定了长度和容量,如果可以的话,尽量加上cap的长度

2.切片与数组的关系

在这里插入图片描述

3.动态扩容

使用append向slice中追加元素,append会根据切片的需要,在当前底层数组容量无法满足的情况下,动态分配新的数组,新数组长度会按一定算法扩展(参见$GOROOT/src/runtime/slice.go中的growslice函数)。新数组建立后,append会把旧数组中的数据复制到新数组中,之后新数组便成为切片的底层数组,旧数组后续会被垃圾回收掉

4.拷贝

使用copy函数,会将源切片的数据逐个拷贝到目的切片指向的数组,拷贝数量取两个切片长度的最小值。

2.3常见问题

在这里插入图片描述

3.Map

map是引用传递,将map类型变量作为函数参数传入不会有很大的性能损耗,并且在函数内部对map变量的修改在函数外部也是可见的

3.1基本操作

判断是否存在的操作,跟遍历的操作,值的位置不一样,要注意区分

//插入数据
m := make(map[K]V)
m[k1] = v1
m[k2] = v2
m[k3] = v3

//获取数据
m := map[string]int
m["key1"] = 1
m["key2"] = 2

v := m["key1"]
fmt.Println(v) // 1

v = m["key3"]
fmt.Println(v) // 0

//判断是否存在
_, ok := m["key"]
if !ok {
    // "key"不在map中
}

//删除
m := map[string]int {
    "key1" : 1,
    "key2" : 2,
}

fmt.Println(m) // map[key1:1 key2:2]
delete(m, "key2")
fmt.Println(m) // map[key1:1]

//遍历
for key,value := range m{

}

map每次遍历的结果可能不一样,不要根据map的遍历去处理有序逻辑。

3.2 数据结构

在这里插入图片描述
count:当前map中的元素个数;对map类型变量运用len内置函数时,len函数返回的就是count这个值。
flags:当前map所处的状态标志,目前定义了4个状态值——iterator、oldIterator、hashWriting和sameSizeGrow。
B:B的值是bucket数量的以2为底的对数,即2^B = bucket数量。
noverflow:overflow bucket的大约数量。
hash0:哈希函数的种子值。
buckets:指向bucket数组的指针。
oldbuckets:在map扩容阶段指向前一个bucket数组的指针。
nevacuate:在map扩容阶段充当扩容进度计数器。所有下标号小于nevacuate的bucket都已经完成了数据排空和迁移操作。
extra:可选字段。如果有overflow bucket存在,且key、value都因不包含指针而被内联(inline)的情况下,该字段将存储所有指向overflow bucket的指针,保证overflow bucket是始终可用的(不被垃圾回收掉)。

bucket的数据结构

真正用来存储键值对数据的是bucket(桶),每个bucket中存储的是Hash值低bit位数值相同的元素,默认的元素个数为BUCKETSIZE(值为8,在$GOROOT/src/cmd/compile/internal/gc/reflect.go中定义,与runtime/map.go中常量bucketCnt保持一致)。当某个bucket(比如buckets[0])的8个空槽(slot)都已填满且map尚未达到扩容条件时,运行时会建立overflow bucket,并将该overflow bucket挂在上面bucket(如buckets[0])末尾的overflow指针上,这样两个bucket形成了一个链表结构,该结构的存在将持续到下一次map扩容

type bmap struct {
	tophash [8]uint8   //存储Hash值的高8位
	data  []byte //key value 数据:key/key/key/...  /value/value/value
	overflow *bmap //溢出bucket的地址
}

tophash存放的是hash值的高8位,对key进行hash之后,低八位相同的放到同一个bucket中,在同一个bucket中可以通过高8位进行查找对应的数据,具体为:当向map插入一条数据或从map按key查询数据的时候,运行时会使用哈希函数对key做哈希运算并获得一个哈希值hashcode。这个hashcode非常关键,运行时将hashcode“一分为二”地看待,其中低位区的值用于选定bucket,高位区的值用于在某个bucket中确定key的位置

3.3 Hash冲突

当有两个或以上数量的键被hash到了同一个bucket时,我们称这些键发生了冲突,Go使用链地址的方法来解决冲突。
当一个bucket存储超过了8个相同键值后,就会使用overflow指向下个bucket,用新的bucket进行存储超出的元素

3.4 负载因子

负载因子 = 键数量/bucket数量

文中详细内容可参考《go语言精进之路》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值