https://www.bilibili.com/video/BV1hv411x7we

string

unicode字符集,每个字符对应01串

定长编码–浪费内存

UTF-8:变长编码,有类似于IP地址分类的前缀

c存储字符串:\0结束

go:go语言认为字符串内容不会被修改

image-20220503160528841

go语言修改字符串

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
修改字符串
注意:字符串是无法被修改的,只能复制原字符串,在复制的版本上修改
方法1:转换为[]byte()
方法2:转换为[]rune()
方法3:新字符串代替原字符串的子字符串,用strings包中的strings.Replace()
*/
func main() {
	//方法1
	s1 := "abcdefgabc"
	s2 := []byte(s1)
	s2[1] = 'B'
	fmt.Println(string(s2)) //aBcdefgabc
	//方法2
	s3 := []rune(s1)
	s3[1] = 'B'
	fmt.Println(string(s3)) //aBcdefgabc
	//方法3
	new := "ABC"
	old := "abc"
	s4 := strings.Replace(s1, old, new, 2)
	fmt.Println(s4) //ABCdefgABC
}

slice

new和make的区别:

二者都是内存的分配(堆上),但是make只用于slice、map以及channel的初始化(非零值);而new用于类型的内存分配,并且内存置为零。所以在我们编写程序的时候,就可以根据自己的需要很好的选择了。

make返回的还是这三个引用类型本身;而new返回的是指向类型的指针。

https://www.kancloud.cn/aceld/golang/1958314

image-20220417194230180

image-20220417194405824

image-20220417194450922

切片的截取

image-20220417194648191

image-20220417194732967

切片的扩容

为啥本来容量为3 但append一个元素容量就为6了呢

image-20220417194946795

image-20220417195158373

image-20220417195255919

结构体和内存对齐

image-20220503164904166

image-20220503165022037

CPU支持访问任意地址,是内部做了一些操作

image-20220503164613343

可以优化这个struct

golang map

hash桶的确认

在map中对key的定位会先使用hash函数获取hash值,写入位置 index = hash%m,得到真正的写入位置。但是在golang中对index的定位有一点小优化,golang使用位与操作进行bucket的定位:

index = hash & (m-1) 为了保证key落在区间[0,m-1],所以要保证m是2的整幂次,如果m是5,那么1-3注定是空桶。

取模操作相对于位与操作,cpu的开销更大

image-20220503170033649

扩容

image-20220504204110225

image-20220503170659691

hmap

image-20220504203212827

bmap

image-20220504203315174

bmap中比较特殊的是kv的存储,bmap将8个key存在一起,将8个value存在一起,是key/key/….value/value…这种存储,相对于将key/value/key/value这种存储设计,将key和value分开存储能使bmap内存布局更加紧密,只需要在最后增加padding对齐即可。

eg:可以计算下map[int64]int8 这种类型的map在上述两种存储设计中每个bmap分别占用的空间大小。

如果将kv存在一起,总共需要的每对kv后需要填充7字节的padding,总共的padding为7*8 = 56字节

但是如果将kv分开存储,8个key是对齐的,8个value也是对齐的,不需要额外的padding填充。

image-20220504203758702

溢出桶

image-20220504203530873

常见的坑点

1、为了防止开发人员依赖map的顺序,对于每一次map遍历(range),得到结果的顺序都是不同的,因为在初始化迭代器(runtime.mapiterinit)时都对startBucket和startCell进行了random操作。

2、无法比较的类型无法作为key

3、map的value是无法寻址的,也就是说无法获取map value的地址。

常见的case为:当value是struct类型,其值无法修改(最简单的解决方法就是value存成*T类型即可);对map[key]取地址时报错。

https://www.kancloud.cn/aceld/golang/1958315

4、map不支持并发读写,只能并发读

对于需要并发读写map的场景,常见的解决方案如下:

  • map + sync.RWMutex

  • 采用 sync.map,实现是小粒度的锁+读写分离+原子操作

5、len(map)返回的是map中元素的个数,不是map的容量

6、new出来的map是无法使用的,因为new只是malloc了8字节大小的内存(new返回指针)。

相对的,make 是个语法糖,最终被编译器翻译成runtime.makemap 函数调用,会进行内存开辟和对象初始化操作。

7、通过fmt打印map时,空map和nil map结果是一样的,都为map[]。所以,这个时候别断定map是空还是nil,而应该通过map == nil来判断。

8、超出容量时会自动扩容,但尽量提供一个合理的初始值

9、map作为函数的出参时不需要以指针形式进行传递,因为map本身就是指针,上文中map初始化一节也可以看到runtime.makemap返回的是*runtime.hmap。

10、delete是不会真正地把map的内存释放的,要回收map还是需要设为nil

总结

本文我们首先介绍了map的基本概念,了解到golang的map是基于hash表实现的且冲突解决方式采用链地址法,并简单介绍了map的基本操作。

接下来我们深入源码,分别研究了map的内存模型、哈希函数、初始化、读取、写入、删除、扩容和遍历过程。map中非常核心的概念是桶,桶作为map数据查找和存储的基本单位,每个桶中有8个cell,key的hash值的低位决定了落入哪个bucket,高位则决定了落入哪个cell中,对于超过8个元素的桶,会在后边链上溢出桶做额外存储,而如果有太多的溢出桶或map的空闲容量较小的情况下,则会触发map的扩容,为了防止一次扩容带来的内存抖动和时延开销,map的扩容是渐进式的,会将扩容过程离散到每一次的数据写入操作中。

channel

image-20220504205350063

image-20220504205605072

发送数据

image-20220504205725511

接收数据

image-20220504205917278

多路select

image-20220505090829357

总结

对一个已经被close过的channel进行接收操作依然可以接受到之前已经成功发送的数据

其实你并不需要关闭每一个channel。只有当需要告诉接收者goroutine,所有的数据已经全部发送时才需要关闭channel。不管一个channel是否被关闭,当它没有被引用时将会被Go语言的垃圾自动回收器回收。(不要将关闭一个打开文件的操作和关闭一个channel操作混淆。对于每个打开的文件,都需要在不使用的时候调用对应的Close方法来关闭文件。)

试图重复关闭一个channel将导致panic异常,试图关闭一个nil值的channel也将导致panic异常。关闭一个channels还会触发一个广播机制

nil的channel有时候也是有一些用处的。因为对一个nil的channel发送和接收操作会永远阻塞,在select语句中操作nil的channel永远都不会被select到。

这使得我们可以用nil来激活或者禁用case,来达成处理其它输入或输出事件时超时和取消的逻辑。

堆内存管理1

https://zhuanlan.zhihu.com/p/76802887

https://zhuanlan.zhihu.com/p/308054212

image-20220628160637034

arena span(不同大小) page 内存块

image-20220628160722469 mheap管理整个堆内存,arena对应heapArena结构,span对应mspan结构

mheap中有个全局的mspan管理中心mheap.central,长度为136的数组,数组元素是一个mcentral结构

一个mcentral对应一种mspan规格类型,记录在spanclass中

image-20220628161351442

全局mspan管理中心方便取用各种规格类型的mspan,为了保障多个p之间并发安全,免不了加锁解锁

go语言的每个p都有一个本地小对象缓存

image-20220628161704990

当p需要用到指定规格类型的mspan时,先去本地缓存这里找对应的mspan,如果没有或用完了就去mcentral获取一个放到本地