这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战
预习
- 使用
append
对slice
进行追加有什么问题没?如果slice
太长了呢? - 用到
map
如果需要顺序遍历怎么办? - 如果只需要
chan
来控制开关或者触发事件,什么样的类型更节省内存? - for和range有什么不一样?
优化建议
1. 尽量创建定长的slice
,避免被多次reslice
在清楚知道需要数据长度的情况,make
创建的时候指定容量capacity以避免被resliced to the new element(重切到新元素)**。
因为当我们使用append
对slice
进行追加的时候,若slice
的capacity
小于1024则每次reslice 2倍,大于1024的时候每次reslice 1.25倍。
下面是slice
的reslice过程capacity变化长度示例,来看看默认情况下reslice如何增长capacity。
s := make([]int, 0)
m := make(map[int]struct{}, 0)
for i := 0; i < 2000; i++ {
m[cap(s)] = struct{}{}
s = append(s, i)
}
for k := range m {
fmt.Println("cap", k)
}
这里输出的 cap 排序后为:0,1,2,4,8,16,32,64,128,256,512,1024,1280,1696,2304
这么多次的reslice则对应每次都要将数据copy到新的slice上,这样既浪费时间也浪费空间又浪费时间。在已知slice长度的情况下直接指定长度创建更好。将上面的例子改成如下
// make(type, length, capacity) ,指定容量
s := make([]int, 0, 2000)
则此处输出的cap只有:20000,没有进行reslice,更加快速。
2. 使用chan只想作为一个触发器不需要发送数据,建议使用 chan struct{}
一般不发送数据的chan
建议使用chan struct{}
,这样最节省内存空间,几乎不会有额外内存浪费。
空的结构体struct{}
难倒不占内存空间吗?来看看下面的输出:
fmt.Println(unsafe.Sizeof(struct{}{})) // 输出:0
fmt.Println(unsafe.Sizeof(true)) // 输出:1
其实空结构体struct{}
确实不占内存空间,所以空结构体广泛用来做一些占位符效果的东西。
3. 遍历少用range多用for的下标,range会有副本拷贝
range
遍历有3种写法:只遍历下标、只遍历值、同时遍历下标和值。
// 第一种,只遍历下标
for index := range intSlice {
fmt.Println(intSlice[index])
}
// 第二种,只遍历值,有数据副本拷贝
for _, value := range intSlice {
fmt.Println(value)
}
// 第三种,同时遍历下标和值,有数据副本拷贝
for index, value := range intSlice {
fmt.Println(index, value)
}
// for 的下标遍历,无额外开销
for index := 0; index < len(intSlice) {
fmt.Println(intSlice[index])
}
但是呢!range
遍历值的时候会有数据副本拷贝,也就是额外的开销。这就导致会有性能问题,所以在遍历的过程中尽量少偷懒,多写几个下标的表达式节省更多资源开销也节省时间。
再但是!如果只是迭代下标,那么for和range几乎没有区别。或者数据本身是指针类型,那么使用range也是可以的,因为range的副本直接引用到了指针上,不会有额外开销。
使用技巧
1. 需要顺序遍历map怎么办?排序key
使用map
的时候可能会遇到想要顺序遍历的情况,通常比较简单的做法就是单独存一份key的slice,然后排序key的slice,再遍历slice得到map的value。
有没有更简单的做法呢?目前对map顺序输出还没有什么好的解决办法,毕竟map的实现就是如此,只能借助外力因素来干涉map的输出顺序。如果有更好的解决办法欢迎评论区讨论留言。
复习
- slice 创建指定好容量避免被多次reslice
- 空的数据结构推荐使用**struct{}**不占内存,适合 chan struct{} 作为触发式channel
- range会拷贝数据副本,尽量避免
- 顺序遍历map需要额外开销存储并排序一份map的key