Java 工程师学 Go,最先被低估的往往是标准库。没有 Spring 全家桶,也没有 Apache Commons 补洞——大量日常工作靠 os、io、bufio、encoding/json 就能做完。这一节把写 CLI 和服务时最常用的包过一遍,并对照 Java 的习惯。
n, err := io.Copy(dst, src)
data, err := io.ReadAll(r)io.Reader 只有一个方法 Read([]byte) (int, error)。几乎所有「能读字节」的东西都实现它:文件、HTTP body、压缩流、bytes.Buffer、strings.Reader。
对比 Java:InputStream / Reader 分裂、装饰器层层嵌套(BufferedInputStream → GZIPInputStream → …)。Go 用小接口 + 函数包装:
gz, _ := gzip.NewReader(file)
defer gz.Close()
scanner := bufio.NewScanner(gz) // 任意 Reader 都能套原则:函数参数收 io.Reader/io.Writer,不要收 *os.File。测试时塞 strings.NewReader 即可,不必碰磁盘。
f, _ := os.Open("app.log")
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
line := sc.Text()
_ = line
}
if err := sc.Err(); err != nil {
return err
}默认每行上限约 64KB,超长行要 sc.Buffer(buf, max)。对比 Java 的 BufferedReader.readLine()——语义接近,但 Go 把错误留到循环外用 Err() 检查。
io/fs.FS 抽象了「只读文件系统」:
func Count(fsys fs.FS, root string) error {
return fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
// ...
return nil
})
}测试用 fstest.MapFS,生产用 os.DirFS("."),行为一致。项目 01、10 都依赖这个抽象。
embed 把文件编译进二进制(对比 classpath resource):
//go:embed templates/*
var templatesFS embed.FS单文件部署时,模板、迁移 SQL、静态资源都不用再带目录。项目 11、14 会用到。
永远用 filepath.Join、filepath.Clean、filepath.WalkDir,不要手拼 "/"。Windows 上 \ 与 / 的差异由这个包消化。
注意:path 包是给 URL 路径用的;文件系统路径用 path/filepath。
cmd := exec.CommandContext(ctx, "git", "log", "--oneline")
cmd.Dir = repoDir
out, err := cmd.Output()对比 ProcessBuilder:Go 把 stdout/stderr/退出码模型收得更紧。务必用 CommandContext,这样取消能杀掉子进程。项目 12 用它读 git log。
安全提醒:不要把未校验的用户输入拼进 shell;优先 Command(name, args...) 而不是 bash -c。
type User struct {
ID int64 `json:"id"`
Email string `json:"email"`
Secret string `json:"-"` // 不序列化
}
data, err := json.Marshal(u)
err = json.NewDecoder(r).Decode(&u)对照 Jackson/Gson:
| 点 | Java | Go |
|---|---|---|
| 字段可见性 | getter / @JsonProperty |
导出字段(大写)+ struct tag |
| 未知字段 | 默认忽略或 FAIL_ON_UNKNOWN | Decoder.DisallowUnknownFields() |
| 原始片段 | JsonNode |
json.RawMessage |
| 流式 | Jackson Streaming API | json.Encoder/Decoder |
json.RawMessage 适合「先看 type 字段再决定怎么解」的多态 JSON。
timeout := 5 * time.Second
time.Sleep(timeout)time.Duration 底层是 int64 纳秒,不是浮点秒。写 Sleep(5) 能编译但语义是 5 纳秒——这是从 Java TimeUnit 过来最容易踩的坑之一。API 里超时参数请直接收 time.Duration,别收 int 秒。
| 需求 | 包 |
|---|---|
| 读文件/目录 | os, io/fs |
| 按行扫描 | bufio |
| 打包进二进制 | embed |
| 路径 | path/filepath |
| 子进程 | os/exec |
| JSON | encoding/json |
| 时间与超时 | time |
Go 标准库经常返回“需要调用方关闭”的对象,例如 *os.File、http.Response.Body、*sql.Rows。一般规则是:成功获得资源的一方负责释放。
f, err := fsys.Open(name)
if err != nil {
return err
}
defer f.Close()不要在 Open 失败前就 defer f.Close(),因为此时 f 可能是 nil。也不要在循环中无限堆积 defer;如果要处理大量文件,把单文件逻辑抽成函数,让 defer 在每次调用结束时执行。
- 参数收
*os.File,实际只需要io.Reader。 - 手工拼文件路径或 URL 路径,混用
path与filepath。 - 用
io.ReadAll读取无上限的网络输入。 - 忽略
Scanner.Err()、Rows.Err()这类延迟报告的错误。 - 为了调一个外部命令而启动 shell,平白引入转义和注入问题。
- 用
fstest.MapFS构造三个文件,统计总字节数,整个测试不访问磁盘。 - 把一个只接收
*os.File的函数改为接收io.Reader,然后用strings.NewReader测试。 - 为
bufio.Scanner构造一行超过 64KB 的输入,观察默认失败并调整 buffer。
下一节:CLI 与 slog
配套项目:10 代码行数统计 · 11 脚手架生成器 · 12 Git changelog