215.go面试题
1.统计字母出现的频率
面试官建议使用生产者消费者模型
type LetterFreq map[rune]int
func CountLetters(strs []string, concurrency int) LetterFreq {
if len(strs) < concurrency {
concurrency = len(strs)
}
ch := make(chan rune, concurrency)
result := make(map[rune]int)
wg := sync.WaitGroup{}
lock := sync.Mutex{}
for ; concurrency > 0; concurrency-- {
wg.Add(1)
go func() {
defer wg.Done()
for c := range ch {
lock.Lock()
result[c]++
lock.Unlock()
}
}()
}
go func() {
defer close(ch)
for _, str := range strs {
for _, c := range str {
ch <- c
}
}
}()
wg.Wait()
return LetterFreq(result)
}
2.go切片的问题
不超过原切片长度之前, 切片和原切片公用一个内存地址
func test2() {
x := []int{1, 2, 3}
y := x[:2] // 1.这里y和x公用一个长度slice地址
y = append(y, 50) // 2.使用50将3替换掉了
y = append(y, 60) // 3.这里发现y的长度是3, 加不进去了, 所有有复制一份内存给y
y[0] = 20 // 4.如果不添加60, 会导致x, y一起变, 但是因为添加了60创建了新数组, 所以只改变有
fmt.Printf("x=%#v\n", x)
fmt.Printf("y=%v\n", y)
}
3.优化代码
当切片长度确定时, 可以直接分配定长, 减少扩容
func test3(n int) []Obj {
//refs := make([]*Obj, 0) // 1. 这里的长度n已经确定可以将容量直接确定, 因为slice在容量不够的时候回*2方式进行扩容, 扩容很耗时
//for i := 0; i < n; i++ {
// obj := &Obj{id: i}
// refs = append(refs, obj)
//}
// 解决方法
refs := make([]Obj, n) // 2.直接把返回值, 和refs给改成这样, 那么这些obj申请分配内存的时候回连续, 加快访问效率
for i := 0; i < n; i++ {
refs[i].id = i
}
return refs
}
4.go无buffer chan可能导致的问题
var (
wg sync.WaitGroup
execTime time.Duration = time.Second
)
func finishReq(timeout time.Duration) int {
ch := make(chan int) //1. 这里因为是一个无buffer的chan, 只有当有协程读的时候才能写入, 如果没有协程读会导致死锁
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(execTime) // Simulate time spent
ch <- 42 // 3. 而这里在经过execTime时间之后想向里面写入数据, 但是没人读导致协程卡死
}()
// 2. 下面的select因为timeout<execTime的原因, 走到了下面的case, select语句执行了之后不会循环, 导致没人读
select {
case result := <-ch:
return result
case <-time.After(timeout): // 如果time.After(timeout)超过timeout时间会返回一个chan, 将当前时间写进去
log.Print("Timeout")
return -1
}
}
func test4() {
timeout := 50 * time.Millisecond
log.Printf("Result: %d", finishReq(timeout))
wg.Wait() // 4. 这里还在等待协程执行完成, 但是在第3步已经卡死了, 程序不会结束, 直接导致死锁
}
5.go select随机选取问题
func test5() {
// 问什么? 执行时间超过两秒
stopCh := make(chan struct{})
ticker := time.NewTicker(100 * time.Millisecond) // 1. 创建了一个时间周期的结构, 其中有一个C是一个Chan, 按照传入的时间周期去向C中写入数据
go func() {
time.Sleep(2 * time.Second) // 2. 等待2秒关闭, 读取空管道可以返回一个未初始化的结构体, 如果int返回0, struct{}返回{}
close(stopCh)
}()
for {
// 4. 解决这个问题加一个select判断
select {
case <-stopCh:
log.Print("exit")
return
default: // 5.如果没有default会导致程序卡在上面直到stopCh关闭
}
time.Sleep(time.Second)
log.Print("work hard...")
select { // 3. 比较奇葩的是这里, select语句执行不是按照case顺序执行的, 而是当其中有一个可以取到值就执行, 一次只执行一个case; 当两个管道同时有值时
// 他会随机选取一个(参考: https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-select/)
case <-ticker.C:
log.Print("Next")
case <-stopCh:
log.Print("exit")
return
}
}
}
6.go内存对齐
参考: https://segmentfault.com/a/1190000017527311
type A1 struct {
a int
b string
c int
//d string
}
type A2 struct {
a int
c int
b string
//d string
}
type M1 struct {
a int8
b int64
c int32
}
type M2 struct {
a int8
b int32
c int64
}
func test7() {
a1 := A1{
a: 1,
b: "a",
c: 2,
//d: "d",
}
a2 := A2{
a: 1,
c: 2,
b: "a",
//d: "d",
}
m1 := M1{
a: 1,
b: 2,
c: 3,
}
m2 := M2{
a: 1,
b: 2,
c: 3,
}
fmt.Println(unsafe.Sizeof(a1), unsafe.Sizeof(a2))
fmt.Println(unsafe.Sizeof(m1), unsafe.Sizeof(m2))
}
7. go module declares问题你如何解决
go: finding module for package github.com/uber-go/ratelimit
go: downloading github.com/uber-go/ratelimit v0.2.0
go: finding module for package github.com/uber-go/ratelimit
go: found github.com/uber-go/ratelimit in github.com/uber-go/ratelimit v0.2.0
go: bluebell/middlewares imports
github.com/uber-go/ratelimit: github.com/uber-go/ratelimit@v0.2.0: parsing go.mod:
module declares its path as: go.uber.org/ratelimit
but was required as: github.com/uber-go/ratelimit
参考:https://sunnyos.com/article-show-99.html
当然如果你不想看的的直接给你的go.mod文件中加一句: (写在require括号外面的地方, 执行go mod tidy即可)
replace (直接把but was required as:后面的地址放这里) => (把module declares its path as:后面的地址放这里,记得加上版本)) // indirect
例子:
replace github.com/uber-go/ratelimit v0.2.0 => go.uber.org/ratelimit v0.2.0 // indirect
其他(待整理)
// 1. new 和make的区别?
//new主要是为类型申请内存, 只会返回执行这个内存的指针
//make主要是对内置的channel和slice, map等结构进行初始化
// 区别主要是new返回指向内存的指针, make内置数据结构的初始化
//参考: https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-make-and-new/
https://mp.weixin.qq.com/s/xNdnVXxC5Ji2ApgbfpRaXQ
// 2. 内存管理?
// 3. Golang中参数传递值好还是指针好?
//参考: https://zhuanlan.zhihu.com/p/383007654
// 4. 线程, linux线程模型?
//参考: https://www.cnblogs.com/lxmhhy/p/6041001.html
// 5.Goroutine什么时候会发生阻塞?
// 6. PMG模型中Goroutine有哪几种状态?线程呢?每个线程/协程占用多少内存知道吗?
// 7. 如果Goroutine—直占用资源怎么办,PMG模型怎么解决的这个问题?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异