三色灯控制实现
数组
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或者多个元素组成,由于数组的长度是固定的,所以在Go中很少使用。所以不作过多介绍。
数组的初始化的两种方式:
1 | var r = [3]int{1, 2, 3} |
Slice
本文代码基于Go 1.17
slice 结构体
1 | type slice struct { |
创建切片
1 | func makeslice(et *_type, len, cap int) unsafe.Pointer { |
这边有个unsafe.Pointer
1 | go里面的指针类似于C++里的y |
增加元素以及扩容
slice相比于数组的一大优势就是可以进行扩容,
1 | // growslice handles slice growth during append. |
roundupsize
假设这边传入的是1600*8=12800,那么size是小于_MaxSmallSize(32768),进入下一层条件判断,有12800>1024-8,所以进入else,计算(size-smallSizeMax)/largeSizeDiv=(12800-1024+128-1)/128=92,则size_to_class128[92]=57,class_to_size[57]/8=1696
1 | func roundupsize(size uintptr) uintptr { |
1 | var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 24, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536, 1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768} |
内置的append函数可能使用比appendInt更复杂的内存扩展策略。因此,通常我们并不知道append调用是否导致了内存的重新分配,因此我们也不能确认新的slice和原始的slice是否引用的是相同的底层数组空间。同样,我们不能确认在原先的slice上的操作是否会影响到新的slice。因此,通常是将append返回的结果直接赋值给输入的slice变量,比如x=append(x,y)
Map
在Go语言中,一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中K和V分别对应key和value。map中所有的key都有相同的类型,所有的value也有着相同的类型,但是key和value之间可以是不同的数据类型。其中K对应的key必须是支持==比较运算符的数据类型,所以map可以通过测试key是否相等来判断是否已经存在。虽然浮点数类型也是支持相等运算符比较的,但是将浮点数用做key类型则是一个坏的想法,正如第三章提到的,最坏的情况是可能出现的NaN和任何浮点数都不相等。对于V对应的value数据类型则没有任何的限制。
抽象结构
go/src/cmd/compile/internal/types/type.go
1 |
|
前两个字段分别位Key和Value,并且支持多种数据类型,Bucket是哈希桶,Hmap是map底层使用的hashtable的原始数据结构,Hiter是用于遍历go map的数据结构。
底层结构
hmap
go中map的底层结构是hamp,结构如下:
1 | type hmap struct { |
通过该数据结构也可以知道,map在作为函数传参的时候,实际传递的是一个指针。直接看下面这个例子:
1 | func main(){ |
输出为:
bmap
bmap就是bucket map,它是桶bucket的底层结构,但是在go的源码中并没有显示的定义,下面的结构体中中只是存储了一个tophash。
1 | type bmap struct { |
根据src/cmd/compile/internal/reflectdata/reflect.go可以还原出bmap的结构:
1 | type bmap struct { |
这里需要注意的是为了保证内存对齐,键值对并不是连续存储的,由此我们可以得到一个bucket的抽象内存模型:
mapextra
1 | // mapextra holds fields that are not present on all maps. |
这边需要注意一个问题,当key和elem的长度超过128的时候,go在map中会使用指针存储,如下代码:
1 | if keytype.Width > MAXKEYSIZE { |
而当一个bmap的key和elem的长度都没有超过128的时候,该map的bucket类型会被标注为不含指针,这样GC将不会扫描到该map,这样就会出现问题,因为bucket的底层结构bmap中含有一个指向溢出桶的uintptr类型的指针,不能保证该指针指向的内存区域不会被GC给清理掉,因为GC不扫描bmap结构,就会导致该指针指向的内存区域被GC给清理掉。因此Go在hmap中新增了一个mapextra字段,其中的overflow是一个指向保存所有hamp.buckets的溢出桶地址的slice指针,oldoverflow是指向保存所有hamp.oldbuckets的溢出桶地址的slice指针。并且只有当map的key和elem都不含之争的时候这两个字段才会有效,因此mapextra的设置就是为了解决因为map没有没有被GC扫描掉而导致相关内存被GC释放的问题,当map的key和elem字段是指针的时候,GC会扫描map,也就知道了bmap中指针指向的内存区域是有被引用的,也就不会释放相应的内存。
由此,现在可以得到一个Go的map结构图:
hiter
1 | // 遍历map |