面试题答案
一键面试性能差异
- 字面量初始化切片
- 性能特点:如果已知切片的初始元素值,使用字面量初始化切片是最直接且高效的方式。因为编译器可以在编译期确定切片的容量和初始值,直接将数据放入内存,避免了运行时动态分配内存的开销。
- 示例:
s := []int{1, 2, 3}
- make函数初始化切片
- 性能特点:使用
make
函数初始化切片,会在运行时根据指定的长度和容量分配内存。如果长度和容量设置不合理,后续的追加操作可能会导致多次内存重新分配和数据拷贝,影响性能。但如果能提前预估好切片的大致容量,通过make
设置合适的容量,可以减少动态扩容的次数,提升性能。 - 示例:
- 性能特点:使用
s := make([]int, 0, 10)
这里创建了一个长度为0,容量为10的切片。 3. 先声明再追加元素
- 性能特点:先声明一个空切片,然后通过
append
函数不断追加元素,在追加过程中,如果切片容量不足,会触发动态扩容。每次扩容通常会重新分配内存,将原切片的数据拷贝到新的内存地址,这会带来较大的性能开销。尤其在频繁追加元素且未提前预估容量的情况下,性能会显著下降。 - 示例:
var s []int
s = append(s, 1)
实际应用场景选择
- 已知初始值的场景:如果明确知道切片的初始元素值,优先使用字面量初始化切片。例如,初始化一个配置项列表等场景:
configs := []string{"config1", "config2", "config3"}
- 需要动态增长且能预估容量的场景:当需要动态增长切片且能预估切片最终的大致容量时,使用
make
函数初始化切片,并设置合适的容量。比如,从数据库读取一批数量大致已知的数据:
// 假设预计从数据库读取100条记录
data := make([]Record, 0, 100)
rows, err := db.Query("SELECT * FROM table")
if err!= nil {
// 处理错误
}
defer rows.Close()
for rows.Next() {
var r Record
err := rows.Scan(&r.Field1, &r.Field2)
if err!= nil {
// 处理错误
}
data = append(data, r)
}
- 无法预估容量且动态增长的场景:当无法预估切片的最终容量,且增长相对缓慢时,可以先声明空切片,再通过
append
追加元素。但要注意,尽量避免在循环内部频繁追加元素导致多次扩容。例如,在处理一些不确定数量的用户输入时:
var userInputs []string
for {
var input string
fmt.Print("请输入内容(按q退出):")
fmt.Scanln(&input)
if input == "q" {
break
}
userInputs = append(userInputs, input)
}
在这种场景下,由于每次用户输入的间隔相对较长,不会导致过于频繁的扩容,对性能影响相对较小。