2. 开发小技巧
2.1. 字符串拼接
字符串的拼接建议使用strings.Builder
// A Builder is used to efficiently build a string using Write methods.
字符串高效拼接
下图是我自己电脑跑出来的结果

可以看出预先分配的strings.Builder最为快速且分配次数最少
func builderPreConcat(n int, str string) string {
var builder strings.Builder
builder.Grow(n * len(str))
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
2.2. 切片的使用
切片的本质是对底层数组的抽象
类型展示
func Slice() {
a := [3]int{1, 2, 3} // 长度为3的数组
b := [2]int{1, 2} // 长度为2的数组
c := [...]int{1, 2, 3} // 自动计算长度的数组
d := []int{1, 2, 3, 4} // 切片
a = b // 长度不一致的数组,属于不同类型,无法实现赋值
}
切片操作并不会复制底层数组,而是会创建一个新的切片复用底层数组
func Slice() {
nums := make([]int, 0, 8)
nums = append(nums, 1, 2, 3, 4, 5, 6, 7, 8)
nums2 := nums[2:4]
fmt.Println(nums) // [1 2 3 4 5 6 7 8]
fmt.Println(nums2) // [3 4]
nums2 = append(nums2, 50, 60) // 修改了底层数组!!!!
fmt.Println(nums) // [1 2 3 4 50 60 7 8]
fmt.Println(nums2) // [3 4 50 60]
}
切片操作指南
Go Slice Tricks Cheat Sheet
如果在某些场景中,只用到了底层数组的小部分元素,那么可以尝试使用copy的方式替代re-slice操作,因为re-slice操作不会新建底层数组,导致长时间引用原底层数组,GC无法释放该内存,而copy会新建底层数组,使得原底层数组内存得到释放。
2.3. 空结构体节省内存
空结构体不占据内存空间,因此被广泛作为各种场景下的占位符使用。一是节省资源,二是空结构体本身就具备很强的语义,即这里不需要任何值,仅作为占位符。
func EmptyStruct() {
fmt.Println(unsafe.Sizeof(struct{}{}))
}
// 0
可以使用map[string]struct{}
实现set
,struct{}
仅作占位符
type Set map[string]struct{}
func DoSet() {
s := make(Set)
s["123"] = struct{}{}
for k, v := range s {
fmt.Println(k,v)
}
}
// 123 {}
也可以作为不发送数据的信道(channel)
Go 空结构体 struct{} 的使用
2.4. for和range的性能对比
range返回的是对象的拷贝
func Loop() {
data := []struct {
Num int
}{
{
Num: 1,
},
{
Num: 2,
},
}
for i := 0; i < len(data); i++ {
data[i].Num = data[i].Num + 10
}
fmt.Println(data)
for _, v := range data {
v.Num = v.Num + 1
}
fmt.Println(data)
}
// [{11} {12}]
// [{11} {12}]
range 在迭代过程中返回的是迭代值的拷贝,如果每次迭代的元素的内存占用很低,那么 for 和 range 的性能几乎是一样,例如 []int
。但是如果迭代的元素内存占用较高,例如一个包含很多属性的 struct 结构体,那么 for 的性能将显著地高于 range,有时候甚至会有上千倍的性能差异。对于这种场景,建议使用 for,如果使用 range,建议只迭代下标,通过下标访问迭代值,这种使用方式和 for 就没有区别了。如果想使用 range 同时迭代下标和值,则需要将切片/数组的元素改为指针,才能不影响性能。——《Go 语言高性能编程》for 和 range 的性能比较