数组、Slice、Map、Channel 重点项
- 数组
数组跟切片的区别?
1、切片是指针类型,数组是值类型;
2、数组的赋值形式为值传递,切片的赋值形式为引用传递;
3、数组的长度是固定的,而切片长度可以任意调整(切片是动态的数组)。
- Slice
Slice底层实现?
Slice的底层结构是一个指向数组的指针和长度len、容量cap构成,Slice的数据实际是存在数组中的
Slice的扩容?
Slice的扩容发生在append方法中,当Slice长度不超过容量cap,新增的元素将直接加到数组中;当Slice长度超过容量cap后,会返回一个新的Slice。
go1.18之前的:
如果期望容量大于当前容量的两倍就会使用期望容量;如果当前切片的长度小于 1024 就会将容量翻倍;如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;
go1.18之后的:
如果期望容量大于当前容量的两倍就会使用期望容量;
!如果当前切片的长度小于 256 就会将容量翻倍;
!如果当前切片的长度大于 256 就会每次增加 (25% + 192) 的容量,直到新容量大于期望容量;
并不完全是1.25,比1.25稍大一点点,192
- Map
Map的底层实现?
在go的map实现中,底层结构体是hmap,hmap中有若干哥bucket数组(桶数组)
Bucket数组中每个元素都是bmap结构,也就是桶的结构,每个桶中有8个k-v对,如果8个满了,就会使用overflow连接下一个桶(溢出桶)
对存储及查询来讲,使用的是哈希运算
Go-Map的put流程?
通过key的hash值后“B”位确定是哪一个桶
遍历当前桶,通过key的tophash和hash值,防止key重复,然后找到第一个可以插入的位置存储数据
如果当前桶元素已满,会通过overflow链接创建一个新的桶来存储数据
关于hash冲突:当两个不同的key落在同一个桶中,就发生了哈希冲突。map使用的拉链法,从前朝后找到一个空位,插入进去,如果8个kv满了,就会链接到下一个溢出桶bmap中
Go-Map的get流程?
①计算key的hash值
②通过最后的B位确定在那号通
③根据key对应hash的tophash高8位快速确定在桶的那个位置,找到后
④对比key的完整hash是否匹配,如果匹配获取对应value
⑤如果都没找到,就去连接的下一个溢出桶中找
为什么要多维护一个tophash,高8位?
因为tophash可以快速确定key是否正常,类似缓存措施,高8位都不同,后面就没比较必要了。
Go-Map的扩容方式
扩容有两种形式,一种是等量扩容,另一种是2倍扩容
等量扩容
由于map中不断的put和delete key,桶中可能会有很多断断续续的空位,这些空位会导致连接的bmap溢出桶很长,导致扫描的时间变长。等量扩容是钟整理,把后置位的数据整理到前面。这种情况,元素会重排,但不会换桶
2倍扩容
桶数组不够用了,需要扩容,元素会重排,可能会换桶,B位换了因为
扩容条件:
1.平均每个桶中的数据超过了6.5个,那就意味着当前容量要不足了,发生扩容
2.溢出桶梳理过多。当B<15时,溢出桶大于2的B次幂;B>=15时,溢出桶数量大于2的15次幂
扩容的细节:
1.扩容刚发生时,会先把老数据放在hmap中的oldbuckets
2.每次对map做删改操作时,会触发从oldbucket中迁到bucket的操作【非一次性,分多次】
3.扩容没完成前,每次get或者put遍历数据时,都会先遍历oldbuckets,再遍历bucket
map会检测是否存在并发写
如果检测到并发写会调用runtime.throw(),无法被recover(),直接GG
如果要并发写map必须在业务层面上加锁(sync.Mutex或sync.RWMutext)或使用sync.Map等同步容器
sync.Map的实现原理?
底层使用了两个原生map,一个叫read,仅用于读;一个叫dirty,用于特定情况下存储最新写入的key-balue数据。适合读多写少的场景
read就好比一个高速缓存,需要从map中读数据时,会先检查read里面有没有key,如果有,就通过原子操作读取并返回,这是sync.的读性能高的原因。
写操作:直接写入dirty(负责写的map)
读操作:先读read(负责读操作的map),没有再读dirty(负责写操作的map)
实现原理概括:
1.通过read和dirty两个字段实现数据的读写分离,读的数据存在只读的read中,将最新写入的数据存在dirty字段上
2.读取时会先查询read,不存在再查询dirty,写入时则只写入dirty
3.读取read不需要加锁,但读或者写dirty需要加锁
有一个misses字段来统计read被穿透的次数(被穿透指需要读dirty的情况),超过一定次数则将dirty数据更新到read中(触发条件:misses=len(dirty))
- Channel
channel理解?
管道类型,通过它可以在goroutine之前发送和接受消息。go的CSP的并发模型,也是通过goroutine和channel实现这种同步模式。核心思想是不要同步共享内存来通信。
实现原理?
channel的底层是一个叫hchan的结构体
内部主要有
一个用来保存goroutine间传递数据的循环链表==》buf
然后一个用来继续记录循环链表当前发送或者接受数据的下标值==》senx和recvx
还有一个用来保存像该chan发送和接受输出的goroutine的队列==》sendq和recvq
保证channel写入和读取数据时线程安全的锁。==》lock