刚出炉的小米 Go 开发一面面经,题目很标准,建议收藏!

74 阅读7分钟

好久没分享最新的面经了,分享一下最近咱们训练营的一位同学参加的小米的 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 的优点?

【怎么回答】 可以从这几个方面聊:

  1. 语法简单:没有像 C++ 那么复杂的特性,上手快。
  2. 并发能力强:原生支持 Goroutine,写并发程序非常容易,而且轻量级。
  3. 性能不错:编译型语言,运行速度快,内存占用相对较低。
  4. 工具链完善:自带格式化、测试、文档工具,开发体验好。
  5. 跨平台:编译出来的二进制文件哪都能跑,部署特别方便(不用装依赖环境)。

5. 进程、线程、协程区别?

【怎么回答】

  • 进程:是操作系统资源分配的最小单位,拥有独立的内存空间。切换开销大。
  • 线程:是 CPU 调度的最小单位,共享进程的内存。切换开销比进程小,但还是有内核态的消耗。
  • 协程(Goroutine):是用户态的轻量级线程。由 Go 运行时(Runtime)管理,不需要操作系统介入。启动和切换的代价非常小,几 KB 内存就能起一个,一台机器能跑上百万个。

6. make 和 new 的区别?

【怎么回答】

  • new :用来分配内存的。比如 new(int),它返回的是一个指针,指向对应类型的零值。它可以用于任何类型。
  • make :专门用来给 slice、map、channel 这三种引用类型分配内存并初始化的。它返回的是这三个类型本身(引用),而不是指针。因为这三种类型如果不初始化内部结构是没法用的。

7. Go 的 GC 机制?

【怎么回答】 不需要背特别深涩的源码,把核心流程说清楚就行: Go 目前用的是三色标记法(黑、灰、白)加上混合写屏障

  1. 刚开始所有对象都是白色的。
  2. 从根节点出发,把引用的对象标灰。
  3. 遍历灰色对象,引用的标灰,自己变黑。
  4. 最后剩下的白色对象就是垃圾。 混合写屏障主要是为了减少 STW(Stop The World)的时间,让用户程序和 GC 可以并发运行。

8. defer 的执行顺序?作用?

【怎么回答】

  • 顺序:后进先出(LIFO),像栈一样。最后定义的 defer 最先执行。
  • 作用:主要用于资源释放,比如文件关闭 f.Close()、锁释放 mu.Unlock()、数据库连接关闭等。防止因为中间报错或者 return 导致资源没释放造成泄露。

9. 空结构体占用空间吗?

【怎么回答】 不占用。struct{} 的大小是 0 字节。 有什么用?

  1. 用在 map 里做 Set:map[string]struct{},只关心 key 存不存在,省内存。
  2. 用在 channel 里做信号传递:chan struct{},不发数据,只发信号。

10. MySQL 调优?

【怎么回答】 这是个大话题,可以分几点说:

  1. SQL 层面:避免 SELECT *,尽量用覆盖索引;避免在大表上做复杂的 Join;注意最左前缀原则,别让索引失效。
  2. 索引层面:该建索引的建索引,区分度不高的字段别建。
  3. 表结构层面:字段类型选合适的,比如能用 int 别用 varchar,大字段可以拆分。
  4. 架构层面:读写分离、分库分表(虽然面试不一定问这么深,提一嘴显专业)。
  5. 排查工具:一定要提 EXPLAIN,用它看 SQL 执行计划,有没有走索引。

11. 索引数据结构?与 B 树有什么区别?

【怎么回答】 MySQL InnoDB 默认用的是 B+ 树区别

  • B 树:每个节点都存数据(索引+记录)。
  • B+ 树:只有叶子节点存数据,非叶子节点只存索引(指针)。 为什么选 B+ 树?
  1. 非叶子节点能存更多索引,树更矮胖,磁盘 IO 次数少。
  2. 叶子节点用链表连起来了,非常适合范围查询(比如 id > 10 这种)。

12. Dockerfile 怎么写的?

【怎么回答】 聊聊常用的指令就行:

  • FROM:指定基础镜像(比如 golang:1.20)。
  • WORKDIR:设置工作目录。
  • COPY / ADD:把代码复制进去。
  • RUN:执行命令(比如 go mod downloadgo build)。
  • CMD / ENTRYPOINT:容器启动时运行的命令。 可以顺便提一下多阶段构建(Multi-stage build),先在一个镜像里编译,再把二进制文件复制到另一个很小的镜像(如 alpine)里运行,这样打出来的镜像特别小。

13. Kafka 在哪用的?

【怎么回答】 主要三个场景:

  1. 解耦:生产者和消费者不需要直接依赖,比如订单系统发个消息,库存系统、积分系统自己去消费处理。
  2. 削峰填谷:流量大的时候,消息先堆在 Kafka 里,消费者慢慢处理,防止把后端数据库打挂。
  3. 异步处理:非核心业务(比如发短信、发邮件)扔到消息队列里异步做,提高接口响应速度。

14. 实习或项目中有遇到什么难题吗?

【怎么回答】 这个问题没有标准答案,但回答套路是 STAR 原则

  • S (Situation) :当时是什么背景,业务量多大?
  • T (Task) :遇到了什么具体问题(比如接口慢、数据不一致、OOM)?
  • A (Action) :你怎么分析的?用了什么工具(pprof、explain)?做了什么优化(加缓存、改索引、改架构)?
  • R (Result) :最后效果怎么样?(比如 QPS 提升了多少,延迟降低了多少)。 一定要准备一个具体的故事,别只说“没遇到难题”。

整体看下来,小米这轮面试还是非常看重基础的。特别是 Go 的切片、GC、并发,以及数据库索引,这些都是高频考点。大家平时在学习的时候,不要只顾着写业务代码,底层的原理还是要多琢磨琢磨。

希望这篇面经对你有帮助,祝大家面试顺利,Offer 多多!