1736 字
约 5 分钟
5
S25-J14 · Go 语言题库:与 Java 的对照式答法

适用读者:Java 背景转 Go、或要答"Go 和 Java 有什么不同"的人。
前置:S09(Go 系列)、S25-J07(并发题库)。
覆盖旧文:3506(Go 语言面试题,约 15 个专题、200+ 问,60,244 字符)→ 1 篇/整篇题集。
扩展点:E-S25-044(对照式答法:把 Go 的每题挂到熟悉的 Java 概念上)、E-S25-045(slice/map/channel 三个"必被追问"的底层)、E-S25-046(GMP 与 Java 线程模型的差别)。
上一篇:S25-J13 · 高并发系统设计题:估算 → 瓶颈 → 取舍
下一篇:题库层最后一批 · LLM/Agent 题库(待写)。


1. 一句话结论

答 Go 题的最优姿势是"对照 Java":"Go 的 slice 类似 Java 的 ArrayList 但共享底层数组"——一句话就让面试官知道你两边都懂。只背 Go 语法的人,答不出"为什么这样设计"。

出处:A-3506(Go 语言面试题,15 专题)。


2. 对照表:Go ↔ Java(E-S25-044)

概念 Go Java
并发单元 goroutine(用户态,KB 级栈) 平台线程(MB 级栈)/ 虚拟线程(JDK 21)
调度 GMP(用户态调度器) OS 调度(虚拟线程有 ForkJoin 载体)
通信 channel(CSP) BlockingQueue / 共享内存 + 锁
泛型 1.18 引入(类型参数 + 约束) 泛型(类型擦除)
错误处理 error 返回值 + panic/recover 异常(checked/unchecked)
内存回收 并发三色标记 + 混合写屏障 分代 GC(G1/ZGC)
继承 组合 + 接口(隐式实现) 类继承 + 接口

判据:"Go 没有继承、没有异常、没有分代 GC"——每条都要能说出"那它用什么替代、代价是什么"。


3. 三个必被追问的底层(E-S25-045)

3.1 slice:共享底层数组是双刃剑

s := make([]int, 3, 5)   // len=3 cap=5
s2 := s[1:3]             // 共享底层数组,改 s2[0] 会改 s[1]
s3 := append(s, 9)       // cap 够 → 原地写;不够 → 新数组(此后不共享)
问 答
底层结构 {ptr, len, cap} 三个字
扩容规则 需容量 < 256 翻倍;≥256 按 ~1.25 倍增长(版本相关,1.18 后调整过)
传参会不会改原 slice 修改元素会(共享数组);append 扩容后不会
copy vs append copy 不扩容、目标长度由 len 决定

实测口径:扩容倍率随 Go 版本调整,答题时说"约 1.25 倍 + 版本相关"比给精确值安全。

3.2 map:无序是设计选择

问 答
底层 hmap + bmap 桶(8 个 kv/桶)+ 溢出桶
为什么无序 故意随机化起始桶,防止业务依赖遍历顺序(Go 1.0 起)
并发安全 否。并发读写会 fatal error: concurrent map writes(不是 panic 可 recover)
安全替代 sync.Map(读多写少)或 RWMutex + map
扩容 装载因子 > 6.5 触发翻倍;过多溢出桶触发等量扩容(整理)
渐进式迁移 hmap.oldbuckets,每次操作搬迁 1~2 个桶

加分点:sync.Map 用 read(原子读)+ dirty 双层,适合"读多写少、key 稳定";写多场景性能不如加锁 map。

3.3 channel:关闭与泄漏

问 答
底层 hchan:环形缓冲区 + sendq/recvq 等待队列 + 锁
从已关闭 channel 读 返回零值 + ok=false(不阻塞)
往已关闭 channel 写 panic
重复关闭 panic
常见泄漏 无缓冲 channel 无人接收 → goroutine 永久阻塞;select 缺 default/超时

判据:"channel 不会自己关,谁写谁关,且只能关一次"——三句话答完 90% 的追问。


4. GMP 与 Java 线程模型的差别(E-S25-046)

G (goroutine) → 待执行的任务
M (machine)   → OS 线程
P (processor) → 逻辑处理器,持有本地运行队列(默认 = GOMAXPROCS = CPU 核数)
维度 Go GMP Java 平台线程
调度层 用户态(M:N) 内核态(1:1)
栈 初始 2 KB,可增长 固定 1 MB(默认)
切换成本 低(无需陷入内核) 高(内核态切换)
阻塞处理 netpoller 把阻塞 G 剥离,M 继续跑别的 G 线程阻塞即占一个内核线程
抢占 1.14+ 基于信号的异步抢占 OS 时间片

Java 21 虚拟线程是同类思路(M:N 用户态调度),但 Go 的 channel/select 是语言级原语,Java 靠 BlockingQueue。


5. 高频"陷阱题"速查

题 陷阱
defer 执行顺序 LIFO;参数在 defer 语句处求值,不是函数返回时
两个 nil 接口相等吗 不一定:iface 的 (type,value) 都 nil 才相等;var e error = (*MyErr)(nil) 不等于 nil
%v/%+v/%#v 值 / 带字段名 / 带类型(Go 语法表示)
struct{} 占空间吗 占 0 字节;用于 map[string]struct{} 当 set
传参值还是引用 都是值传递;slice/map/channel 传的是"描述符的副本"
变量在栈还是堆 逃逸分析决定;go build -gcflags='-m' 可看
init() 时机 包级变量初始化后、main 前;同包多文件按文件名顺序
字符串拼接 +(少量)/ strings.Builder(循环)/ bytes.Buffer;Builder 避免多次分配

6. 反例:Java 思维答题的三个坑

说法 问题
"Go 的锁和 Java 一样是重入锁" sync.Mutex 不可重入,重入即死锁
"Go 有 try-catch" 只有 panic/recover,且不能跨 goroutine
"GC 也是分代的" Go 是并发三色标记 + 混合写屏障,不分代(1.19 后有 GC 优化但无分代收集)

7. 自测

  1. slice 扩容后为什么不再共享底层数组?
  2. Go 的 map 为什么故意设计成无序?
  3. 往已关闭 channel 写、重复关闭分别会怎样?
  4. GMP 的 M:N 与 Java 平台线程 1:1 的差别体现在哪?
  5. sync.Mutex 可重入吗?
答案
  1. 容量不足时 append 分配新数组并复制,指针改变。
  2. 防止业务依赖遍历顺序(随机起始桶)。
  3. 写 → panic;重复关闭 → panic。
  4. 用户态调度、栈 KB 级、切换不进内核;阻塞由 netpoller 剥离。
  5. 不可重入,重入会死锁。

8. 下一篇

题库层最后一批 · LLM 与 Agent 题库(约 150 篇编号题,35xx 系列)—— RAG、Agent 记忆、工具调用、评估与部署的答题骨架(待写)。


本篇属于系列《S25 · 求职与面试》。旧文来源:A-3506、A-3484;扩展点 E-S25-044/045/046。容量数字为按公式估算(非线上实测)。

S25-J14 · Go 语言题库:与 Java 的对照式答法
https://www.clxhxhhr.top/posts/5595/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。