好久没分享最新的面经了,分享一下最近咱们训练营的一位同学参加的小米的 Go 开发一面,整体面下来感觉题目还是比较典型和基础的,涵盖了 Go 语言基础、数据库、中间件以及容器化等常见考察点。
今天我就把这次的面试题整理出来,并且附上一些回答思路,希望能帮到正在准备面试的各位。
1. 自我介绍
【怎么回答】 这个环节不用背简历,主要是快速让面试官认识你。建议控制在 2-3 分钟,简单说说你的教育背景,重点介绍你熟悉的各种技术栈(特别是和岗位匹配的 Go、MySQL、Redis 等),然后挑一两个你觉得最有亮点的项目简单提一句,引导面试官往你熟悉的地方问。
2. 手撕:二叉树层序遍历
【怎么回答】 这是个非常经典的算法题。核心思路就是用队列(Queue)。 先把根节点入队,然后只要队列不为空,就拿出队头元素,打印或者存起来,然后把它的左孩子和右孩子依次入队。这样一层一层地处理,就是层序遍历了。
代码大概长这样:
func levelOrder(root *TreeNode) [][]int {
if root == nil {
return nil
}
var res [][]int
queue := []*TreeNode{root}
for len(queue) > 0 {
var level []int
l := len(queue)
for i := 0; i < l; i++ {
node := queue[0]
queue = queue[1:] // 出队
level = append(level, node.Val)
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
}
res = append(res, level)
}
return res
}
3. 数组和切片的区别?
【怎么回答】 这题是 Go 基础必问。
- 数组(Array):长度是固定的,定义的时候就确定了,属于值类型。你把它传给函数,它是会完全拷贝一份的。
- 切片(Slice):长度是动态的,它底层引用了一个数组。切片是一个结构体,包含三个字段:指针、长度(len)和容量(cap)。它是引用类型的语义(虽然传递时是拷贝结构体,但底层指向同一个数组),扩容时会自动处理。
4. Go 的优点?
【怎么回答】 可以从这几个方面聊:
- 语法简单:没有像 C++ 那么复杂的特性,上手快。
- 并发能力强:原生支持 Goroutine,写并发程序非常容易,而且轻量级。
- 性能不错:编译型语言,运行速度快,内存占用相对较低。
- 工具链完善:自带格式化、测试、文档工具,开发体验好。
- 跨平台:编译出来的二进制文件哪都能跑,部署特别方便(不用装依赖环境)。
5. 进程、线程、协程区别?
【怎么回答】
- 进程:是操作系统资源分配的最小单位,拥有独立的内存空间。切换开销大。
- 线程:是 CPU 调度的最小单位,共享进程的内存。切换开销比进程小,但还是有内核态的消耗。
- 协程(Goroutine):是用户态的轻量级线程。由 Go 运行时(Runtime)管理,不需要操作系统介入。启动和切换的代价非常小,几 KB 内存就能起一个,一台机器能跑上百万个。
6. make 和 new 的区别?
【怎么回答】
new:用来分配内存的。比如new(int),它返回的是一个指针,指向对应类型的零值。它可以用于任何类型。make:专门用来给 slice、map、channel 这三种引用类型分配内存并初始化的。它返回的是这三个类型本身(引用),而不是指针。因为这三种类型如果不初始化内部结构是没法用的。
7. Go 的 GC 机制?
【怎么回答】 不需要背特别深涩的源码,把核心流程说清楚就行: Go 目前用的是三色标记法(黑、灰、白)加上混合写屏障。
- 刚开始所有对象都是白色的。
- 从根节点出发,把引用的对象标灰。
- 遍历灰色对象,引用的标灰,自己变黑。
- 最后剩下的白色对象就是垃圾。 混合写屏障主要是为了减少 STW(Stop The World)的时间,让用户程序和 GC 可以并发运行。
8. defer 的执行顺序?作用?
【怎么回答】
- 顺序:后进先出(LIFO),像栈一样。最后定义的 defer 最先执行。
- 作用:主要用于资源释放,比如文件关闭
f.Close()、锁释放mu.Unlock()、数据库连接关闭等。防止因为中间报错或者 return 导致资源没释放造成泄露。
9. 空结构体占用空间吗?
【怎么回答】
不占用。struct{} 的大小是 0 字节。
有什么用?
- 用在
map里做 Set:map[string]struct{},只关心 key 存不存在,省内存。 - 用在
channel里做信号传递:chan struct{},不发数据,只发信号。
10. MySQL 调优?
【怎么回答】 这是个大话题,可以分几点说:
- SQL 层面:避免
SELECT *,尽量用覆盖索引;避免在大表上做复杂的 Join;注意最左前缀原则,别让索引失效。 - 索引层面:该建索引的建索引,区分度不高的字段别建。
- 表结构层面:字段类型选合适的,比如能用
int别用varchar,大字段可以拆分。 - 架构层面:读写分离、分库分表(虽然面试不一定问这么深,提一嘴显专业)。
- 排查工具:一定要提
EXPLAIN,用它看 SQL 执行计划,有没有走索引。
11. 索引数据结构?与 B 树有什么区别?
【怎么回答】 MySQL InnoDB 默认用的是 B+ 树。 区别:
- B 树:每个节点都存数据(索引+记录)。
- B+ 树:只有叶子节点存数据,非叶子节点只存索引(指针)。 为什么选 B+ 树?
- 非叶子节点能存更多索引,树更矮胖,磁盘 IO 次数少。
- 叶子节点用链表连起来了,非常适合范围查询(比如
id > 10这种)。
12. Dockerfile 怎么写的?
【怎么回答】 聊聊常用的指令就行:
FROM:指定基础镜像(比如golang:1.20)。WORKDIR:设置工作目录。COPY/ADD:把代码复制进去。RUN:执行命令(比如go mod download,go build)。CMD/ENTRYPOINT:容器启动时运行的命令。 可以顺便提一下多阶段构建(Multi-stage build),先在一个镜像里编译,再把二进制文件复制到另一个很小的镜像(如 alpine)里运行,这样打出来的镜像特别小。
13. Kafka 在哪用的?
【怎么回答】 主要三个场景:
- 解耦:生产者和消费者不需要直接依赖,比如订单系统发个消息,库存系统、积分系统自己去消费处理。
- 削峰填谷:流量大的时候,消息先堆在 Kafka 里,消费者慢慢处理,防止把后端数据库打挂。
- 异步处理:非核心业务(比如发短信、发邮件)扔到消息队列里异步做,提高接口响应速度。
14. 实习或项目中有遇到什么难题吗?
【怎么回答】 这个问题没有标准答案,但回答套路是 STAR 原则:
- S (Situation) :当时是什么背景,业务量多大?
- T (Task) :遇到了什么具体问题(比如接口慢、数据不一致、OOM)?
- A (Action) :你怎么分析的?用了什么工具(pprof、explain)?做了什么优化(加缓存、改索引、改架构)?
- R (Result) :最后效果怎么样?(比如 QPS 提升了多少,延迟降低了多少)。 一定要准备一个具体的故事,别只说“没遇到难题”。
整体看下来,小米这轮面试还是非常看重基础的。特别是 Go 的切片、GC、并发,以及数据库索引,这些都是高频考点。大家平时在学习的时候,不要只顾着写业务代码,底层的原理还是要多琢磨琢磨。
希望这篇面经对你有帮助,祝大家面试顺利,Offer 多多!