面试题答案
一键面试切片和map作为函数实参的传递机制
- 切片:
- 在Go语言中,切片是一个引用类型。当切片作为函数实参传递时,传递的是切片的指针(虽然Go语言中没有指针操作符
*
来显式表示),本质上传递的是一个包含指向底层数组的指针、切片长度和容量的结构体。 - 例如:
- 在Go语言中,切片是一个引用类型。当切片作为函数实参传递时,传递的是切片的指针(虽然Go语言中没有指针操作符
package main
import "fmt"
func modifySlice(s []int) {
s[0] = 100
}
func main() {
mySlice := []int{1, 2, 3}
modifySlice(mySlice)
fmt.Println(mySlice)
}
- 在上述代码中,
modifySlice
函数中对切片的修改会反映到原切片上,因为传递的是指向底层数组的指针等信息。
- map:
- map同样是引用类型。当map作为函数实参传递时,传递的是map的引用,即指向map数据结构的指针。
- 例如:
package main
import "fmt"
func modifyMap(m map[string]int) {
m["key"] = 200
}
func main() {
myMap := make(map[string]int)
myMap["key"] = 100
modifyMap(myMap)
fmt.Println(myMap["key"])
}
- 这里
modifyMap
函数对map的修改也会体现在原map上,因为传递的是map的引用。
性能优化
- 切片:
- 预分配内存:在创建切片时,如果能提前预估切片的大小,使用
make
函数预分配足够的内存可以避免在添加元素时频繁的内存重新分配。例如mySlice := make([]int, 0, 100)
,这里预先分配了容纳100个int
类型元素的内存,后续添加元素时只要不超过容量就不会重新分配内存。 - 减少不必要的复制:由于切片传递的是引用,在函数内部对切片操作尽量避免创建新的切片(如果不需要新的独立数据结构),以免增加内存开销。
- 预分配内存:在创建切片时,如果能提前预估切片的大小,使用
- map:
- 预分配内存:和切片类似,创建map时可以预分配内存。例如
myMap := make(map[string]int, 100)
,这样可以减少在添加元素时哈希表重新分配内存和重新哈希的开销。 - 合理选择键类型:选择合适的键类型,避免使用大的结构体作为键,因为键在哈希计算和比较时会有额外开销。尽量使用基本类型(如
int
、string
)作为键。
- 预分配内存:和切片类似,创建map时可以预分配内存。例如
高并发场景下的性能问题及解决
- 切片:
- 性能问题:高并发场景下,多个goroutine同时读写切片可能会导致数据竞争问题,例如一个goroutine在读取切片元素时,另一个goroutine同时修改切片长度或底层数组,可能导致程序崩溃或数据不一致。
- 解决方法:
- 互斥锁:使用
sync.Mutex
来保护对切片的读写操作。例如:
- 互斥锁:使用
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
var sharedSlice []int
func writeSlice(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
sharedSlice = append(sharedSlice, 1)
mu.Unlock()
}
func readSlice(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
fmt.Println(sharedSlice)
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go writeSlice(&wg)
}
for i := 0; i < 5; i++ {
wg.Add(1)
go readSlice(&wg)
}
wg.Wait()
}
- **读写锁**:如果读操作远多于写操作,可以使用`sync.RWMutex`,读操作使用读锁,写操作使用写锁,以提高并发性能。
2. map:
- 性能问题:在高并发场景下,多个goroutine同时读写map同样会导致数据竞争问题,Go语言的map本身不是线程安全的,直接在高并发下读写会导致程序崩溃。
- 解决方法:
- sync.Map:Go 1.9引入了
sync.Map
,它是线程安全的map实现,适合在高并发场景下使用。例如:
- sync.Map:Go 1.9引入了
package main
import (
"fmt"
"sync"
)
var sharedMap sync.Map
func writeMap(wg *sync.WaitGroup) {
defer wg.Done()
sharedMap.Store("key", 1)
}
func readMap(wg *sync.WaitGroup) {
defer wg.Done()
value, ok := sharedMap.Load("key")
if ok {
fmt.Println(value)
}
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go writeMap(&wg)
}
for i := 0; i < 5; i++ {
wg.Add(1)
go readMap(&wg)
}
wg.Wait()
}
- **使用互斥锁**:和切片类似,也可以使用`sync.Mutex`来保护对map的操作,但`sync.Map`在高并发下性能通常更好。