make 方法
make是 用于创建和初始化切片(slice)、映射(map)和通道(channel) 的内置函数。
1. 切片(Slice)
// 语法:make([]T, length, capacity)
// capacity(容量)可选,不写默认等于 length
slice1 := make([]int, 5) // 长度5,容量5,初始值 [0,0,0,0,0]
slice2 := make([]string, 0, 10) // 长度0,容量10(预分配空间,追加时不频繁扩容)
何时用:当你需要动态扩容或用 append 添加元素,但想预先指定长度或容量时。
2. 映射(Map)
// 语法:make(map[KeyType]ValueType, hint)
// hint(容量提示)可选,用于预估大小以提升性能
m1 := make(map[string]int) // 空的 map,可直接读写
m2 := make(map[int]bool, 100) // 预估有100个键值对,减少扩容开销
注意:直接用 var m map[string]int 得到的是 nil,读取时会返回零值(不报错),但写入会引发 panic,所以必须用 make 或字面量 {} 初始化。
3. 通道(Channel)
// 语法:make(chan T, bufferSize)
ch1 := make(chan int) // 无缓冲通道(同步通信)
ch2 := make(chan bool, 5) // 有缓冲通道(容量5,异步通信)
何时用:协程间通信时,用 make 创建通道实例。
接口和类型
- 即便是业务开发,也应该面向接口编程
- 结构体和结构体的字段都遵循大小写控制访问性的原则
接口
接口是一组行为的抽象
type List interface {
Add(index int, val any)
Append(val any)
Delete(index int)
}
结构体
new 可以理解为 Go 会为你的变量分配内存,并且把内存都置为0。
// 初始化语法
type User struct {}
// 获取指针
- &User{}
- new(User)
注: Go 没有构造函数
方法接收器
一个方法可以定义在这个结构体上,也可以定义在这个结构体的指针上。
- 结构体接收器内部修改字段,不会生效。因为方法调用本身是值传递的。
- 如果是基本类型,结构体,那么就相当于复制了一份。
- 如果是指针,那么就复制了一份指针(也就是地址),但是指向的结构体还是同一个。
- 内置类型复制了一份,但是它们的内部数据结构,还是同一个。
- 如果在结构体内部还要引用自身,那么只能使用指针。
type User struct {
Name string
Age int
}
// 结构体接收器
func (u User) ChangeName(name string) {
u.Name = name
}
// 指针接收器
func (u *User) ChangeAge(age int) {
u.Age = age
}
defer 实现机制
defer用于延迟执行一个函数调用,直到包含它的函数执行完毕(无论正常返回还是发生panic)后才执行。它在资源管理、错误处理和代码结构清晰化方面非常关键。
defer 的内部实现分成三种机制:
-
堆上分配:是指整个 defer 直接分配到堆上,缺点就是要被 GC 管理。
-
栈上分配:整个 defer 分配到了 goroutine 栈上,不需要被 GC 管理。比堆上分配性能提升了
30%。
-
开放编码(Open Code):启用内联的优化,你直观理解就是相当于把你的 defer 内容放到了你的函数最后。启用条件:
-
函数的 defer 数量少于或者等于 8 个;
-
函数的 defer 关键字不能在循环中执行;
-
函数的 return 语句与 defer 语句的乘积小于或者等于 15 个。
-
切片扩容
切片扩容的原理非常简单,就是重新分配一段连续内存,而后把原本的数据拷贝过去。
切片扩容的系数:
- 当容量小于 256 的时候,两倍扩容;
- 否则,按照 1.25 扩容。
- 低版本是 1024 作为分界点。
📈 为什么扩容倍数从“2倍”变为“1.25倍”?
这是一个早期版本(Go 1.18之前)的简单策略,旨在平衡性能和内存。
- 小容量翻倍 (2x) :当切片较小时(容量 < 1024),采用2倍扩容。这样做的好处是增长速度快,能迅速减少频繁的内存分配次数,提升性能。
- 大容量1.25倍 (1.25x) :当切片较大时(容量 >= 1024),采用更保守的1.25倍扩容。这能避免过度申请内存,节省宝贵的系统资源。
🎯 为什么阈值从“1024”变为“256”?
在 Go 1.18 中将阈值从 1024 降低到了 256,并采用了一套新的计算方式:
- 新策略:当容量小于 256 时,依然保持 2 倍扩容;当容量大于等于 256 时,新的容量
m通过循环计算m += (m + 3*256) / 4得到。 - 效果:新公式让扩容系数从 2.0 倍开始,平滑地过渡到接近 1.25 倍,避免了突变。
选择 256 作为阈值,是为了在向一个超大切片追加元素时,使总的内存重分配次数与旧策略大致相当。简单说,就是在“小切片高效增长”和“大切片节约内存”之间找到了一个更好的平衡点。