关于Go的Channel,Silce,Map
Posted 小生凡一
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于Go的Channel,Silce,Map相关的知识,希望对你有一定的参考价值。
1. Chan
1.1 channel 数据结构
type hchan struct
qcount uint // 队列中的全部元素个数
dataqsiz uint // 底层循环数组的长度
buf unsafe.Pointer
// 缓冲链表 buf 指向底层循环数组,只有缓冲型的 channel 才有。环型的缓存
elemsize uint16 // chan中的元素大小
closed uint32 // chan是否被关闭的标识
elemtype *_type // chan 中元素类型
sendx uint // 已 发送元素 在循环数组中的索引
recvx uint // 已 接受元素 在循环数组中的索引
recvq waitq // 等待接收的 goroutine 队列
sendq waitq // 等待发送的 goroutine 队列
lock mutex // 保护所有元素
type waitq struct
first *sudog
last *sudog
我们可以看到chan其实是有锁的。所以是并发安全的。
1.2 读取已经关闭channel的有缓冲通道
- 有缓冲的话,会先把通道中的值给读出来,之后就会变成读取的是零值。
ch := make(chan int,3)
for i:=0; i<3; i++
ch <- i
close(ch)
for c := range ch
fmt.Println(c) // 0 1 2
1.3 如果有一个300缓冲的channel,在2核的服务器上,读写300个数据,如何设置线程数,并发数?
1.4 注意点
- select 中的break无法跳出for循环
- for + select closed channel 会死循环
2. Slice
2.1 Slice 和 Array的区别
- Slice 是可变的,Array 是不可变的
- Slice的底层就是用Array实现的
2.2 Append 原理
Append 就是如果slice不需要扩容,那么就先进行扩容,再进行append
func append(slice []Type,elems ...Type) []Type
这里传递参数是值拷贝,因此append无法修操作原切片
。值为结构体的情况下考虑使用指针。返回一个新的切片。
- 错误
func main()
var array []int
array = Append(array)
fmt.Println(array) // []
func Append(s []int)
s = append(s, 1)
- 正确
func main()
var array []int
array = Append(array)
fmt.Println(array) // [1]
func Append(s []int)[]int
s = append(s, 1)
return s
例子:
func Append(s[]int)
//s[0] = 1024 //① 位置1
s = append(s,100)
s = append(s,200)
//s[0] = 1024 //② 位置2
// fmt.Println(s)
func main()
var s []int
for i:=0; i<3; i++
s = append(s,i)
Append(s)
fmt.Println(s)
① 位置1:
我们在main函数中的输出结果是 [1024 1 2] 我们的append是值拷贝,是不会进行修改原切片
② 位置2:
main函数中输出结果:[0 1 2] 因为这里其实发生了扩容,那么已经是扩容了,那么扩容部分就是另一块内存了。所以就没有修改成功。
2.3 命名区别
1. var a []int
// 定义一个 []int 类型的变量 a , 并分配内存空间给 a 赋 []int 的 nil。
2. a := []int
// 定义一个 []int 类型的变量 a , 并分配内存空间给 a , 但是不为nil。
3. a := make([]int,3)
// 分配内存函数,仅用于 chan、map 和 slice,返回的数据类型就是这三个类型本身
// chan、map 和 slice 这三种类型本身就是引用类型,所以没必要返回指针类型
4. a := new(int)
// 声明一个 int 型指针 a,分配一个 int 的内存空间,并把内存地址给 a。
3. map
推荐这篇博文 Map底层实现
3.1 底层结构
type hmap struct
// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
// Make sure this stays in sync with the compiler's definition.
count int // 键值对的数量
flags uint8
// 状态标识,比如正在被写、buckets和oldbuckets在被遍历、等量扩容(Map扩容相关字段)
B uint8 // 2^B=len(buckets)
noverflow uint16 // 溢出桶里bmap大致的数量
hash0 uint32 // hash 因子
buckets unsafe.Pointer
// 指向一个数组(连续内存空间),数组的类型为[]bmap,这个字段我们可以称之为正常桶
oldbuckets unsafe.Pointer
// 扩容时,存放之前的buckets(Map扩容相关字段)
nevacuate uintptr
// 分流次数,成倍扩容分流操作计数的字段(Map扩容相关字段)
extra *mapextra
// 溢出桶结构,正常桶里面某个bmap存满了,会使用这里面的内存空间存放键值对
3.2 赋值
var mp1 map[int][int] // 1
mp1[1]=1
mp2 := map[int][int] // 2
mp2[1]=1
情况 1 :报错,var的赋值 mp1是为nil的。
情况 2 :可行,而第二种情况的赋值是不为nil的。
3.3 map 是线程安全的吗?
map的底层hmap是没有加锁的。所以并不是线程安全的。
3.4 map的扩容注意点
具体扩容涉及到的太多了,这里过多讲解。可
以下写法是错误的,因为当map扩容的时候,他的扩容地址会发生变化,所以key value都是不可取地址。并且map删除key之后是不会自动缩容的。
m:=make(map[int][int])
m[1]=1
fmt.Println(&m[1])
以上是关于关于Go的Channel,Silce,Map的主要内容,如果未能解决你的问题,请参考以下文章