Appearance
15|标准库常用包
Go 的标准库覆盖了文件操作、JSON 处理、时间计算、命令行参数解析和日志输出等场景。很多运维和后端工具不需要引入第三方包,单靠标准库就能完成核心功能。本章介绍最常用的五个包:encoding/json、os/io、time、flag 和 log/slog。net/http 会在后端开发篇详细展开,这里只做简单提及。
一、JSON 解析与生成
解析 JSON
把 JSON 字节流解析到结构体:
go
package main
import (
"encoding/json"
"fmt"
)
type Host struct {
Name string `json:"name"`
IP string `json:"ip"`
Role string `json:"role"`
}
func main() {
raw := []byte(`{"name":"web01","ip":"192.168.10.11","role":"web"}`)
var host Host
if err := json.Unmarshal(raw, &host); err != nil {
fmt.Println("parse failed:", err)
return
}
fmt.Println(host.Name, host.IP)
}json.Unmarshal 的第二个参数必须是指针,因为它需要把解析结果写进变量。
生成 JSON
把结构体转成 JSON 字节流:
go host
data, err := json.Marshal(host)
if err != nil {
fmt.Println("marshal failed:", err)
return
}
fmt.Println(string(data))json.MarshalIndent 生成带缩进的格式,适合打印或写入文件:
go data,
忽略字段和自定义格式
json:"-" 表示跳过该字段;omitempty 表示零值时跳过:
go
type Result struct {
Name string `json:"name"`
Cost time.Duration `json:"-"` // 不输出
CostMilli int64 `json:"cost_ms,omitempty"` // 0 时跳过
}time.Duration 直接序列化会变成纳秒数字,不可读。通常把它转成毫秒或秒再输出,内部字段用 json:"-" 隐藏。
二、文件和系统操作
os 包提供文件读写、目录操作、环境变量和进程信息:
go
package main
import (
"fmt"
"os"
)
func main() {
// 读取整个文件
data, err := os.ReadFile("/etc/hosts")
if err != nil {
fmt.Println("read failed:", err)
return
}
fmt.Println(string(data))
// 写入文件
err = os.WriteFile("/tmp/output.txt", []byte("hello"), 0644)
if err != nil {
fmt.Println("write failed:", err)
}
}os.ReadFile 适合小文件,一次性读进内存。大文件用 os.Open 返回的 *os.File 配合缓冲区逐块读取,避免内存耗尽。
检查文件是否存在:
go _,
if os.IsNotExist(err) {
fmt.Println("file not found")
}os.IsNotExist 只判断"文件不存在"错误。权限不足、路径不是普通文件等情况需要额外处理。
三、io 包
io 包定义了通用的读写接口。最常用的是 io.Copy,把数据从一个 Reader 复制到 Writer:
go
import "io"
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}io.Copy 内部会分配缓冲区,不需要手动管理。复制大文件时,它的内存占用是稳定的。
io.MultiWriter 把数据同时写到多个目标,常用于"写文件同时打印到屏幕":
go file,
defer file.Close()
writer := io.MultiWriter(file, os.Stdout)
fmt.Fprintln(writer, "log message")四、时间处理
time 包处理时间和定时器:
go
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now.Format("2006-01-02 15:04:05"))
// 解析时间字符串
t, err := time.Parse("2006-01-02", "2024-06-01")
if err != nil {
fmt.Println("parse failed:", err)
return
}
fmt.Println(t)
}Go 的时间格式化用参考时间 "2006-01-02 15:04:05",而不是 "YYYY-MM-DD" 这种占位符。这个设计来自 Go 诞生时间(2006年1月2日15时04分05秒),需要死记硬背。
计算耗时用 time.Since:
go start
// ... 执行操作
cost := time.Since(start)
fmt.Println("cost:", cost)time.Since 等价于 time.Now().Sub(start),但代码更短。
五、命令行参数
标准库 flag 处理简单参数:
go
package main
import (
"flag"
"fmt"
)
func main() {
host := flag.String("host", "127.0.0.1", "target host")
port := flag.Int("port", 80, "target port")
verbose := flag.Bool("verbose", false, "verbose output")
flag.Parse()
fmt.Printf("host=%s port=%d verbose=%v\n", *host, *port, *verbose)
}运行:
bash
go run . -host 192.168.10.11 -port 8080 -verboseflag 返回的是指针,使用时需要解引用(*host)。参数很多、需要子命令时,可以考虑 cobra 这类第三方库。小工具先用标准库 flag 足够。
flag.Parse() 必须在所有参数定义之后调用,通常在 main 开头。如果参数解析失败(比如传了未定义的参数),程序会打印帮助信息并退出。
六、结构化日志
Go 1.21 以后标准库有 log/slog,支持结构化日志:
go
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
logger.Info(
"check result",
"host", "web01",
"service", "nginx",
"status", "ok",
)
}输出:
json
{"time":"2024-06-01T10:00:00Z","level":"INFO","msg":"check result","host":"web01","service":"nginx","status":"ok"}结构化日志比纯字符串更适合被日志平台检索。字段名稳定后,按 host、service、status 筛选和聚合都更方便。日志字段名一旦确定,后续不要随意变更,否则历史日志和新日志的字段对不上。
七、常见错误
JSON tag 字段名未导出
go
type Host struct {
name string `json:"name"` // 首字母小写,反射不可见
}序列化后 name 字段为空。这是新手最常见的 JSON 问题之一。
time.Parse 格式写错
go
t, err := time.Parse("2024-01-01", "2024-06-01") // 错误:格式串应该是参考时间time.Parse 的第一个参数不是示例时间,而是参考时间 "2006-01-02 15:04:05" 的变体。写错后解析结果完全不对,且不会报错(只会返回解析失败)。
flag 在 Parse 之后定义
go
flag.Parse()
host := flag.String("host", "", "target host") // 太晚,不会被解析flag.Parse() 只解析在它之前定义的标志。后面定义的标志拿不到值,始终用默认值。
忽略 os.ReadFile 的错误
go data,
文件不存在时 data 是 nil,后续操作可能 panic。