主题
第11章 面试题精选 —— 标准库
★ 基础题
Q1:Go 的 io.Reader 和 io.Writer 设计为什么这么牛?
答:两个最小接口,把所有数据流抽象掉了。
go
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }- 文件、网络、内存 buffer、HTTP body……都实现了它们
io.Copy(dst Writer, src Reader)一行代码搞定任何拷贝- 设计哲学:接口越小,越能被复用
举例:把 HTTP 响应直接保存到文件:
go
io.Copy(file, resp.Body)Q2:encoding/json 怎么忽略字段?怎么处理空值?
go
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"` // 空时不输出
Password string `json:"-"` // 永不输出
Age int `json:"age,string"` // 编码成 "30" 而不是 30
}注意:omitempty 对 0、""、nil、false、空 slice/map 都视为空。
Q3:怎么写一个最简单的 HTTP 服务器?
go
package main
import "net/http"
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello"))
})
http.ListenAndServe(":8080", nil)
}5 行代码,没有任何第三方框架。
Q4:Go 的时间格式化为什么用 2006-01-02 15:04:05?
答:这是 Go 设计的"魔法时间",记忆口诀:
01-02 03:04:05 PM '06 -0700
1 2 3 4 5 PM 6 7按数字 1 2 3 4 5 6 7 排列,对应:月-日 时:分:秒 PM 年 时区。
比起其他语言的
%Y-%m-%d风格,Go 的方案更"无歧义"——不会把月当成日。
Q5:os.Exit、log.Fatal、panic 有什么区别?
| 触发 defer? | 打日志? | 退出码 | |
|---|---|---|---|
os.Exit(1) | ❌ | ❌ | 1 |
log.Fatal(err) | ❌(内部调 os.Exit) | ✅ | 1 |
panic("err") | ✅ | ✅(堆栈) | 2 |
生产代码:通常 main 函数里只用 log.Fatal,业务里用 error 或 panic(仅限"不可恢复"场景)。
★★ 进阶题
Q6:bufio.Scanner 和 bufio.Reader 怎么选?
- Scanner:按行/词读取,每行不能超过 64KB(默认)
- Reader:通用缓冲读,可读任意长度
读大文件按行:
go
sc := bufio.NewScanner(f)
sc.Buffer(make([]byte, 1024*1024), 10*1024*1024) // 提升上限到 10MB
for sc.Scan() {
line := sc.Text()
}Q7:HTTP 客户端怎么设置超时?
至少 3 处可设:
go
client := &http.Client{
Timeout: 10 * time.Second, // 1. 整体超时(推荐)
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 3 * time.Second, // 2. 建连超时
}).DialContext,
TLSHandshakeTimeout: 3 * time.Second, // 3. TLS 握手
ResponseHeaderTimeout: 5 * time.Second,
},
}或用 context(更细粒度):
go
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, ...)Q8:Response.Body 必须 Close 吗?为什么?
必须:
go
resp, err := http.Get(url)
if err != nil { return err }
defer resp.Body.Close() // 一定要!否则:
- 底层 TCP 连接无法复用,导致连接泄漏
- 对端服务可能无法及时回收资源
- 长期运行会耗尽 fd
即使你不读 Body 也要 Close。建议
io.Copy(io.Discard, resp.Body)把 body 读完,TCP 才能 keep-alive 复用。
Q9:context.Context 的常用四件套是?
go
context.Background() // 根 context(main、初始化用)
context.TODO() // 不知道用什么时(占位)
context.WithCancel(parent) // 主动取消
context.WithTimeout(parent, 5*time.Second) // 超时取消
context.WithDeadline(parent, t) // 到点取消
context.WithValue(parent, key, val) // 携带请求级数据使用规范:
- 第一个参数永远叫
ctx - 不要把 ctx 存到 struct(除非是 server)
- 调
cancel()一定要 defer
Q10:log/slog 比 log 强在哪?
- 结构化输出:每个字段是 key=value,方便日志收集
- 支持级别:Debug、Info、Warn、Error
- 可切 JSON Handler,对接 ELK / Loki 直接食用
- 支持 LogGroup / WithAttrs:批量加上下文(如 trace_id)
go
slog.Info("user login", "user", "alice", "ip", "1.2.3.4")
// time=... level=INFO msg="user login" user=alice ip=1.2.3.4Go 1.21+ 新增。生产推荐 slog 或 zap、zerolog。
★★★ 深度题
Q11:怎么实现一个反向代理?
net/http/httputil 自带:
go
import "net/http/httputil"
target, _ := url.Parse("http://backend:8080")
proxy := httputil.NewSingleHostReverseProxy(target)
http.ListenAndServe(":80", proxy)几十行就能写一个简易 Nginx。
Q12:encoding/json 的性能瓶颈在哪?怎么优化?
瓶颈:反射。每次 Marshal/Unmarshal 都要遍历 struct 字段类型。
优化方案:
- 复用 buffer:
json.NewEncoder(w)比json.Marshal(...)+Write节省一次 alloc - 预编译生成代码:
easyjson、ffjson直接生成静态序列化代码 - 第三方高性能库:
json-iterator/go:API 兼容、~5x 性能bytedance/sonic:JIT、~10x(仅 amd64)goccy/go-json:纯 Go、~3x
Q13:HTTP/2 在 Go 里怎么开?
默认 TLS 服务自动开 HTTP/2:
go
http.ListenAndServeTLS(":443", "cert.pem", "key.pem", handler)
// HTTP/1.1 + HTTP/2 (h2)明文 HTTP/2 需要 golang.org/x/net/http2/h2c。
客户端可以用 http2.Transport{} 显式配置。
Q14:如何"优雅关闭" HTTP 服务?
go
srv := &http.Server{Addr: ":8080", Handler: h}
go srv.ListenAndServe()
// 监听信号
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<-sig
// 给 30 秒处理已有请求
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
srv.Shutdown(ctx) // 不再接受新连接,等已有请求完成关键:用
srv.Shutdown(ctx)而不是srv.Close()。
Q15:embed 是什么?怎么用?
Go 1.16 引入,编译期把静态资源打进二进制:
go
import "embed"
//go:embed assets/*
var assets embed.FS
func main() {
http.Handle("/static/", http.FileServer(http.FS(assets)))
http.ListenAndServe(":80", nil)
}构建出的二进制文件自带 HTML/CSS/图片,单文件部署超方便。
Q16:testing 包怎么写性能基准测试?
go
func BenchmarkAppend(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
s := make([]int, 0, 1000)
for j := 0; j < 1000; j++ {
s = append(s, j)
}
}
}bash
go test -bench=. -benchmem输出:
BenchmarkAppend-8 500000 2400 ns/op 8192 B/op 1 allocs/op📌 速记
io 抽象一切;json 看 tag;http 自带框架;time 用魔法时间;context 传超时;slog 写结构化日志。
🎯 自测练习
- 写一个工具:把 stdin 的内容同时写到 stdout 和
out.txt(用 io.MultiWriter) - 把一个有嵌套 struct 的对象序列化为带缩进的 JSON
- 写一个 HTTP 服务,支持 GET /users 返回所有用户、POST /users 添加
- 写个 5 秒超时的 HTTP 客户端,超过则取消
- 用 slog + JSON handler 写一行带 request_id 的日志
进入第12章 →