slice是数组的快照,slice底层数据是一个结构体,包含三个元素,长度、容量和数组指针。所以slice的赋值就如同结构体的赋值一样,slice的应用其实都是对数组指针的操作。注意点:对slice的操作会影响到所有引用到底层数组的slice。 func sliceModify(slice []int) { newSlice := slice newSlice[0] = 88 } func main() { array := [5]int{1, 2, 3, 4, 5} slice := []int{1, 2, 3, 4, 5} arrayModify(array) sliceModify(slice) fmt.Println(array) fmt.Println(slice) }
在slice末尾添加元素,且长度加1,如果长度没有超过原数组的容量,则返回的还是指向原数组的slice,否则就是指向新数组的slice。 map是由桶数组组成,map会把key通过hash得到的字符串分成两部分,一部分第八位作为数组的下标,一部分作为桶内高八位hash数组的值 ,桶外通过数组映射,桶内通过遍历数组元素总数为8的hash数组。map除此之外还有B扩容次数,溢出桶、及哈希种子等比较重要的结构。 channel有一块缓存环状线性链表,锁,容量,进出的索引值和阻塞的协程列表(分为取或塞的两种协程列表)。chan操作环状线性链表都需加锁,chan通过索引值判断能取或者塞是否能进行,当不能取或塞时,就用阻塞的协程列表保存当前协程。比如当chan中没有可取的元素时,执行取操作,会阻塞当前协程,将协程加入到chan的取阻塞协程列表。当有一个协程执行塞的操作,chan会从取阻塞列表中取一个协程,将其放入就绪协程队列,以此来唤醒协程。
channel重要的是阻塞协程列表和索引,用于在常数时间内实现chan的取和塞的操作。一索引,chan缓存是一个线性结构,通过索引*每个元素大小就能在缓存中找到对应的取塞位置。二通过存储阻塞协程,能迅速唤醒协程。 select本质上通过数组实现,数组中存储case结构,有chan、操行的类型kind和接受结构体elem。首先select数组随机打乱顺序,然后,循环取数组中的元素执行操作,如chan1执行取操作,判断chan1是否有元素(通过chan内部字段),如果没有就将当前协程加入到chan1的阻塞列表。循环完成后,当前协程G1挂起。
如上一节chan那样,chan1会在有塞操作的时候,自动将G1送回协程就绪队列,之后协程G1被调度到,又进行一次上面的操作。
go的select的机制就和操作系统层面的select是一模一样的,同样是异步通知,但不知道是哪一个事件已经就绪,需要都遍历一遍。