面试题答案
一键面试数组与切片选择
- 固定长度场景(选择数组)
- 场景描述:当业务明确知道数据的固定数量,例如游戏中固定数量的玩家队伍成员(如5人一队),或者固定大小的缓存池(如固定100个缓存单元)。
- 选择原因:数组在声明时就确定了大小,内存布局是连续的,访问效率高。对于固定长度需求,数组不会浪费额外内存,也避免了动态扩容带来的开销。
- 动态增长场景(选择切片)
- 场景描述:在日志收集系统中,日志数量会随着时间不断增加,事先无法确定最终的日志量;或者在实时数据处理系统中,不断接收来自各种传感器的数据。
- 选择原因:切片是动态数组,它基于数组实现,但可以动态增长。在需要动态添加元素时,切片可以根据需要自动扩容,使用起来更加灵活。
性能优化策略
- 内存管理
- 数组:由于数组大小固定,一旦声明,其占用的内存就固定下来。在内存使用上比较简单直接,只要确定好数组大小,不会有额外的内存分配和释放开销。例如,如果需要存储1000个固定大小的结构体,可以直接声明
[1000]MyStruct
。 - 切片:切片在扩容时会重新分配内存,将原数据复制到新的内存空间。为了减少这种内存重新分配和复制的开销,可以预先估计数据量,使用
make
函数指定切片的容量。例如,s := make([]int, 0, 1000)
,这样预先分配了容纳1000个整数的内存,在后续添加元素时,如果数量不超过1000,就不会触发重新分配内存的操作。
- 数组:由于数组大小固定,一旦声明,其占用的内存就固定下来。在内存使用上比较简单直接,只要确定好数组大小,不会有额外的内存分配和释放开销。例如,如果需要存储1000个固定大小的结构体,可以直接声明
- 并发访问
- 数组:如果多个协程需要并发访问数组,由于数组本身不是线程安全的,需要使用
sync.Mutex
进行保护。例如:
- 数组:如果多个协程需要并发访问数组,由于数组本身不是线程安全的,需要使用
var mu sync.Mutex
var arr [10]int
func writeToArr(index, value int) {
mu.Lock()
arr[index] = value
mu.Unlock()
}
- **切片**:同样,切片本身也不是线程安全的,并发访问时也需要使用`sync.Mutex`等同步机制。另外,在并发环境下,如果切片涉及到动态扩容,还需要注意扩容操作的原子性。可以通过将切片操作封装在一个带有锁的函数中来保证线程安全。例如:
var mu sync.Mutex
var s []int
func appendToSlice(value int) {
mu.Lock()
s = append(s, value)
mu.Unlock()
}
此外,在高并发场景下,还可以考虑使用sync.RWMutex
,如果读操作远多于写操作,可以使用读锁提高并发读的效率,减少锁竞争。