15-字节飞书面试真题解析 (Go 语言版)

💡 背景:这是一份 26 届校招/实习(外包)的真实面经,包含一二面真题。虽然原题包含 Java 问题,但我们可以将其转化为 Go 语言视角的回答。


🟢 一面 (基础夯实)

1. 计算机网络

Q1: 七层/四层网络模型

  • OSI 七层:物理层、数据链路层、网络层 (IP)、传输层 (TCP/UDP)、会话层、表示层、应用层 (HTTP)。
  • TCP/IP 四层:网络接口层、网络层、传输层、应用层。
  • 面试话术:“OSI 是理论模型,TCP/IP 是实际标准。平常我们主要关注传输层(TCP)和应用层(HTTP)。“

Q2: TCP 的可靠性怎么保证?

  • 关键词:校验和、序列号/确认应答(ACK)、超时重传、流量控制(滑动窗口)、拥塞控制(慢启动/快重传)。
  • 一句话:“通过给包编号、丢包重传、以及控制发送速度不把对方和网络塞满,来保证可靠性。“

Q3: HTTP 1.1 vs 2.0 vs HTTPS

  • HTTP 1.1:长连接 (Keep-Alive),但有队头阻塞。
  • HTTP 2.0多路复用 (一个连接并发处理多个请求),头部压缩 (HPACK),服务端推送。
  • HTTPS:HTTP + SSL/TLS 加密(握手耗时但安全)。

2. 操作系统 (OS)

Q1: 线程状态流转

  • 新建 (New) → 就绪 (Runnable) ↔ 运行 (Running) → 阻塞 (Blocked) / 等待 (Waiting) → 终止 (Terminated)。
  • Go 视角:Go 只有 GMP 状态(Running, Runnable, Waiting, Syndcall 等),没有”新建”,goroutine 一创建就是 Runnable。

Q2: 进程/线程通信方式 (IPC)

  • 管道 (Pipe):单向,父子进程用。
  • 消息队列:有格式的数据块。
  • 共享内存:最快,需要加锁。
  • 信号量 (Semaphore):控制访问数量。
  • Socket:不同机器通信(K8s/微服务常用)。

3. MySQL 数据库

Q1: 索引下推 (ICP)

  • 是什么:MySQL 5.6 后,把部分过滤逻辑”下推”给存储引擎,减少回表次数。
  • 例子Select * from user where name like '张%' and age = 10
    • 无 ICP:引擎查出所有姓张的 ID → 回表查所有字段 → Server 层过滤 age=10。
    • 有 ICP:引擎查姓张的同时,顺便过滤 age=10 → 只有符合的才回表。

Q2: B+ 树 vs B 树

  • B+ 树 (MySQL):数据全在叶子节点,叶子之间有指针串起来(适合范围查询,如 id > 100)。
  • B 树:数据在所有节点上,范围查询要中序遍历,不仅慢还对 CPU 缓存不友好。

4. 语言特性 (Java Go 迁移)

Q1: ThreadLocal (Java) 在 Go 里对应什么?

  • Java ThreadLocal:每个线程独立的存储空间。
  • Go 观点Go 官方不推荐 Goroutine Local Storage (GLS)
  • Go 替代方案
    1. Context:显式传递参数(推荐,清晰明了)。
    2. 第三方库 (gls):黑科技获取 GID,容易造成内存泄漏,尽量别用。

5. 设计模式

Q1: 单例模式 (Go 写法)

  • 饿汉式var instance = &Singleton{} (init 阶段创建,天然安全)。
  • 懒汉式 (双重检查)sync.Once (Go 里的满分答案)。
var once sync.Once
var instance *Singleton
 
func GetInstance() *Singleton {
    once.Do(func() {
        instance = &Singleton{}
    })
    return instance
}

6. 手撕代码

Q1: 最长回文子串 (LeetCode 5)

  • 思路:中心扩散法。遍历字符串,以每个字符为中心向两边扩散。
  • 注意:奇数长度 aba (中心是 b) 和偶数长度 abba (中心是 bb 缝隙) 都要考虑。

🔴 二面 (深度与实战)

1. Redis

Q1: ZSet 查 member score 的复杂度?

  • O(1)
  • 原理:ZSet 底层同时用了 跳表 (SkipList)哈希表 (Hash)
    • 查排名/范围用跳表 (O(logN))。
    • 查具体元素 Score 用哈希表 (O(1))。

Q2: Redis 哈希表扩容 (Rehash)

  • 渐进式 Rehash
    • 不会一次性把几百万数据搬过去(会卡死主线程)。
    • 而是分批搬运:每次用户访问(增删改查)时,顺便搬一点;如果不访问,也有定时任务搬一点。
    • 搬运期间,新数据写新表,旧数据查旧表(找不到再去新表)。

2. MySQL

Q1: 联合索引与”最左前缀”

  • 索引 (name, age)
  • where name='xx' and age=10 (走索引)
  • where name='xx' (走索引)
  • where age=10 (不走索引,因为不知道 name)
  • 姓名 vs 性别谁在左?
    • 区分度高的在左边!姓名的区分度远高于性别,所以 (name, gender) 更好。

3. 操作系统

Q1: 管道是什么进程都可以通信吗?

  • 匿名管道 (|):只能用于父子进程或兄弟进程(有血缘关系)。
  • 命名管道 (FIFO):任何进程都可以。

4. SQL 手撕

Q1: 查出刚好选两门课的学生 ID

SELECT student_id
FROM course_selection
GROUP BY student_id
HAVING COUNT(course_id) = 2;

5. 手撕代码

Q1: K 个一组翻转链表 (LeetCode 25 - Hard)

  • 这是面试里的”顶级难度”题之一,考察逻辑严密性。
  • Go 思路
    1. 统计长度,算出能翻转几组。
    2. 遍历每组,用”头插法”或”局部翻转”。
    3. 连接每组的头尾。
func reverseKGroup(head *ListNode, k int) *ListNode {
    dummy := &ListNode{Next: head}
    pre := dummy
    
    // 1. 计算长度
    length := 0
    for p := head; p != nil; p = p.Next {
        length++
    }
    
    // 2. 翻转每组
    for length >= k {
        cur := pre.Next
        next := cur.Next
        for i := 0; i < k-1; i++ {
            cur.Next = next.Next
            next.Next = pre.Next
            pre.Next = next
            next = cur.Next
        }
        pre = cur
        length -= k
    }
    return dummy.Next
}

💡 总结与建议

这份面经非常典型,涵盖了互联网大厂基础岗的核心考点:

  1. 基础过硬:OS、网络、数据库原理问得很细(如 Rehash 细节)。
  2. 不考偏门:除了 Java 题需要转换思路,其他都是通用计算机基础。
  3. 手撕看重逻辑:链表翻转是经典,建议熟练背诵 K 个一组翻转 的模板。