大家好,我是极客老墨。

Go 标准库是个宝藏。fmt 格式化输出、strings 字符串处理、time 时间操作、json 序列化、os 文件读写,这些日常开发 80% 的需求都能搞定,不用到处找第三方库。

今天精选几个最常用的包,看看它们怎么用,能解决什么问题。

fmt:格式化输入输出

fmt 包用于格式化输出和输入,是最常用的包之一。

基本输出

 1import "fmt"
 2
 3func main() {
 4    // 输出不换行
 5    fmt.Print("Hello")
 6    fmt.Print("World")
 7
 8    // 输出换行
 9    fmt.Println("Hello")
10    fmt.Println("World")
11
12    // 格式化输出
13    name := "Go"
14    age := 15
15    fmt.Printf("Name: %s, Age: %d\n", name, age)
16}

常用占位符

 1// 字符串
 2fmt.Printf("%s\n", "hello")      // hello
 3fmt.Printf("%q\n", "hello")      // "hello" (带引号)
 4
 5// 整数
 6fmt.Printf("%d\n", 42)           // 42 (十进制)
 7fmt.Printf("%b\n", 42)           // 101010 (二进制)
 8fmt.Printf("%x\n", 42)           // 2a (十六进制)
 9
10// 浮点数
11fmt.Printf("%f\n", 3.14)         // 3.140000
12fmt.Printf("%.2f\n", 3.14159)    // 3.14 (保留2位)
13
14// 布尔值
15fmt.Printf("%t\n", true)         // true
16
17// 通用
18fmt.Printf("%v\n", 42)           // 42 (默认格式)
19fmt.Printf("%+v\n", struct{X int}{42}) // {X:42} (带字段名)
20fmt.Printf("%#v\n", []int{1,2})  // []int{1, 2} (Go语法)
21fmt.Printf("%T\n", 42)           // int (类型)

格式化字符串

1// Sprintf 返回字符串
2msg := fmt.Sprintf("Hello, %s!", "World")
3fmt.Println(msg) // Hello, World!
4
5// Errorf 创建错误
6err := fmt.Errorf("failed to open file: %s", "config.json")

要点

  • Print 系列直接输出
  • Printf 系列格式化输出
  • Sprintf 系列返回字符串
  • %v 是万能占位符,适合调试

strings:字符串处理

strings 包提供了丰富的字符串操作函数。

常用操作

 1import "strings"
 2
 3func main() {
 4    str := " Hello World "
 5
 6    // 去除前后空格
 7    fmt.Println(strings.TrimSpace(str))  // "Hello World"
 8
 9    // 包含判断
10    fmt.Println(strings.Contains(str, "World")) // true
11
12    // 前缀后缀
13    fmt.Println(strings.HasPrefix(str, " Hello")) // true
14    fmt.Println(strings.HasSuffix(str, "World ")) // true
15
16    // 查找位置
17    fmt.Println(strings.Index(str, "World"))     // 7
18    fmt.Println(strings.LastIndex(str, "o"))     // 8
19
20    // 计数
21    fmt.Println(strings.Count("hello", "l"))     // 2
22}

字符串转换

 1// 大小写转换
 2fmt.Println(strings.ToUpper("hello"))  // HELLO
 3fmt.Println(strings.ToLower("HELLO"))  // hello
 4fmt.Println(strings.Title("hello world")) // Hello World
 5
 6// 替换
 7fmt.Println(strings.Replace("hello", "l", "L", 1))  // heLlo (替换1次)
 8fmt.Println(strings.ReplaceAll("hello", "l", "L")) // heLLo (全部替换)
 9
10// 重复
11fmt.Println(strings.Repeat("Go", 3))   // GoGoGo

分割和连接

 1// 分割
 2parts := strings.Split("a,b,c", ",")
 3fmt.Println(parts) // [a b c]
 4
 5fields := strings.Fields("  hello  world  ")
 6fmt.Println(fields) // [hello world]
 7
 8// 连接
 9result := strings.Join([]string{"a", "b", "c"}, "-")
10fmt.Println(result) // a-b-c

字符串构建器

1// 频繁拼接字符串用 Builder
2var builder strings.Builder
3
4for i := 0; i < 5; i++ {
5    builder.WriteString("Go")
6}
7
8result := builder.String()
9fmt.Println(result) // GoGoGoGoGo

要点

  • 字符串是不可变的
  • 频繁拼接用 strings.Builder
  • Fields 按空白字符分割,Split 按指定分隔符分割

strconv:字符串转换

strconv 包用于字符串和基本类型之间的转换。

字符串转数字

 1import "strconv"
 2
 3// 字符串转整数
 4i, err := strconv.Atoi("42")
 5if err != nil {
 6    fmt.Println("Error:", err)
 7}
 8fmt.Println(i) // 42
 9
10// 字符串转 int64
11i64, err := strconv.ParseInt("42", 10, 64)
12fmt.Println(i64) // 42
13
14// 字符串转浮点数
15f, err := strconv.ParseFloat("3.14", 64)
16fmt.Println(f) // 3.14
17
18// 字符串转布尔值
19b, err := strconv.ParseBool("true")
20fmt.Println(b) // true

数字转字符串

 1// 整数转字符串
 2s := strconv.Itoa(42)
 3fmt.Println(s) // "42"
 4
 5// int64 转字符串
 6s = strconv.FormatInt(42, 10)
 7fmt.Println(s) // "42"
 8
 9// 浮点数转字符串
10s = strconv.FormatFloat(3.14, 'f', 2, 64)
11fmt.Println(s) // "3.14"
12
13// 布尔值转字符串
14s = strconv.FormatBool(true)
15fmt.Println(s) // "true"

要点

  • AtoiItoa 是最常用的转换函数
  • Parse 系列返回 error,需要检查
  • Format 系列不返回 error

time:时间处理

time 包提供了时间和日期的操作。

获取时间

 1import "time"
 2
 3// 当前时间
 4now := time.Now()
 5fmt.Println(now)
 6
 7// 指定时间
 8t := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
 9fmt.Println(t)
10
11// Unix 时间戳
12timestamp := time.Now().Unix()
13fmt.Println(timestamp) // 1704067200
14
15// 从时间戳创建时间
16t = time.Unix(timestamp, 0)
17fmt.Println(t)

时间格式化

 1now := time.Now()
 2
 3// Go 的格式化模板:2006-01-02 15:04:05
 4fmt.Println(now.Format("2006-01-02"))           // 2024-01-12
 5fmt.Println(now.Format("2006-01-02 15:04:05"))  // 2024-01-12 14:30:00
 6fmt.Println(now.Format("15:04:05"))             // 14:30:00
 7fmt.Println(now.Format("2006/01/02"))           // 2024/01/12
 8
 9// 解析时间字符串
10t, err := time.Parse("2006-01-02", "2024-01-12")
11if err != nil {
12    fmt.Println("Error:", err)
13}
14fmt.Println(t)

⚠️ 重要:Go 的时间格式化使用固定的参考时间:2006-01-02 15:04:05(助记:1月2号3点4分5秒06年)

时间计算

 1now := time.Now()
 2
 3// 加减时间
 4later := now.Add(2 * time.Hour)
 5earlier := now.Add(-30 * time.Minute)
 6
 7fmt.Println(later)
 8fmt.Println(earlier)
 9
10// 时间差
11duration := later.Sub(now)
12fmt.Println(duration) // 2h0m0s
13
14// 比较时间
15if later.After(now) {
16    fmt.Println("later is after now")
17}
18
19if earlier.Before(now) {
20    fmt.Println("earlier is before now")
21}

时间间隔

 1// 常用时间单位
 2fmt.Println(time.Second)      // 1s
 3fmt.Println(time.Minute)      // 1m0s
 4fmt.Println(time.Hour)        // 1h0m0s
 5
 6// 自定义间隔
 7duration := 2*time.Hour + 30*time.Minute
 8fmt.Println(duration) // 2h30m0s
 9
10// 睡眠
11time.Sleep(time.Second)

定时器

 1// 一次性定时器
 2timer := time.NewTimer(2 * time.Second)
 3<-timer.C
 4fmt.Println("Timer expired")
 5
 6// 周期性定时器
 7ticker := time.NewTicker(time.Second)
 8defer ticker.Stop()
 9
10for i := 0; i < 3; i++ {
11    <-ticker.C
12    fmt.Println("Tick", i)
13}

encoding/json:JSON 处理

json 包用于 JSON 的编码和解码。

结构体转 JSON

 1import "encoding/json"
 2
 3type User struct {
 4    ID    int    `json:"id"`
 5    Name  string `json:"name"`
 6    Email string `json:"email,omitempty"` // 空值时省略
 7    Age   int    `json:"-"`               // 忽略该字段
 8}
 9
10func main() {
11    user := User{
12        ID:    1,
13        Name:  "Alice",
14        Email: "alice@example.com",
15        Age:   30,
16    }
17
18    // 序列化
19    data, err := json.Marshal(user)
20    if err != nil {
21        fmt.Println("Error:", err)
22        return
23    }
24
25    fmt.Println(string(data))
26    // {"id":1,"name":"Alice","email":"alice@example.com"}
27}

JSON 转结构体

 1func main() {
 2    jsonStr := `{"id":1,"name":"Alice","email":"alice@example.com"}`
 3
 4    var user User
 5    err := json.Unmarshal([]byte(jsonStr), &user)
 6    if err != nil {
 7        fmt.Println("Error:", err)
 8        return
 9    }
10
11    fmt.Printf("%+v\n", user)
12    // {ID:1 Name:Alice Email:alice@example.com Age:0}
13}

格式化输出

1user := User{ID: 1, Name: "Alice"}
2
3// 格式化输出(带缩进)
4data, _ := json.MarshalIndent(user, "", "  ")
5fmt.Println(string(data))
6// {
7//   "id": 1,
8//   "name": "Alice"
9// }

处理 map 和 slice

 1// map 转 JSON
 2m := map[string]interface{}{
 3    "name": "Go",
 4    "age":  15,
 5}
 6data, _ := json.Marshal(m)
 7fmt.Println(string(data)) // {"age":15,"name":"Go"}
 8
 9// slice 转 JSON
10s := []string{"a", "b", "c"}
11data, _ = json.Marshal(s)
12fmt.Println(string(data)) // ["a","b","c"]

JSON 标签

1type Product struct {
2    ID    int    `json:"id"`                    // 指定字段名
3    Name  string `json:"name"`
4    Price float64 `json:"price,omitempty"`      // 空值省略
5    Stock int    `json:"-"`                     // 忽略
6    Tags  []string `json:"tags,omitempty"`
7}

要点

  • 只有导出字段(首字母大写)才会被序列化
  • 使用 JSON 标签控制序列化行为
  • omitempty 省略零值字段
  • "-" 忽略字段

os:操作系统接口

os 包提供了操作系统功能的接口。

文件操作

 1import "os"
 2
 3// 读取文件
 4data, err := os.ReadFile("config.txt")
 5if err != nil {
 6    fmt.Println("Error:", err)
 7    return
 8}
 9fmt.Println(string(data))
10
11// 写入文件
12err = os.WriteFile("output.txt", []byte("Hello"), 0644)
13if err != nil {
14    fmt.Println("Error:", err)
15}
16
17// 打开文件
18file, err := os.Open("config.txt")
19if err != nil {
20    fmt.Println("Error:", err)
21    return
22}
23defer file.Close()
24
25// 创建文件
26file, err = os.Create("new.txt")
27if err != nil {
28    fmt.Println("Error:", err)
29    return
30}
31defer file.Close()

目录操作

 1// 创建目录
 2err := os.Mkdir("mydir", 0755)
 3if err != nil {
 4    fmt.Println("Error:", err)
 5}
 6
 7// 创建多级目录
 8err = os.MkdirAll("path/to/dir", 0755)
 9
10// 删除文件或目录
11err = os.Remove("file.txt")
12
13// 删除目录及其内容
14err = os.RemoveAll("mydir")
15
16// 重命名
17err = os.Rename("old.txt", "new.txt")

环境变量

 1// 获取环境变量
 2home := os.Getenv("HOME")
 3fmt.Println(home)
 4
 5// 设置环境变量
 6os.Setenv("MY_VAR", "value")
 7
 8// 获取所有环境变量
 9for _, env := range os.Environ() {
10    fmt.Println(env)
11}

sort:排序与查找

sort 包提供了切片排序和查找的功能。

基本排序

 1import "sort"
 2
 3// 整数排序
 4nums := []int{5, 2, 8, 1, 9}
 5sort.Ints(nums)
 6fmt.Println(nums) // [1 2 5 8 9]
 7
 8// 字符串排序
 9strs := []string{"banana", "apple", "cherry"}
10sort.Strings(strs)
11fmt.Println(strs) // [apple banana cherry]

自定义排序

 1type Person struct {
 2    Name string
 3    Age  int
 4}
 5
 6people := []Person{{"Alice", 30}, {"Bob", 25}}
 7sort.Slice(people, func(i, j int) bool {
 8    return people[i].Age < people[j].Age
 9})
10fmt.Println(people) // [{Bob 25} {Alice 30}]

二分查找

1nums := []int{1, 2, 5, 8, 9}
2idx := sort.SearchInts(nums, 5)
3if idx < len(nums) && nums[idx] == 5 {
4    fmt.Printf("Found at index %d\n", idx)
5}

要点

  • IntsStrings 等函数原地排序
  • Slice 支持自定义比较函数
  • SearchInts 要求切片已排序

math:数学运算

math 包提供了基本的数学函数和常量。

常用常量

1import "math"
2
3fmt.Println(math.Pi)      // 3.141592653589793
4fmt.Println(math.E)       // 2.718281828459045
5fmt.Println(math.Sqrt2)   // 1.4142135623730951
6fmt.Println(math.MaxInt)  // 最大 int 值
7fmt.Println(math.MinInt)  // 最小 int 值

常用函数

 1// 幂运算和开方
 2fmt.Println(math.Pow(2, 3))   // 8 (2 的 3 次方)
 3fmt.Println(math.Sqrt(16))    // 4 (平方根)
 4fmt.Println(math.Cbrt(27))    // 3 (立方根)
 5
 6// 绝对值和取整
 7fmt.Println(math.Abs(-5))     // 5
 8fmt.Println(math.Ceil(3.7))   // 4 (向上取整)
 9fmt.Println(math.Floor(3.7))  // 3 (向下取整)
10fmt.Println(math.Round(3.5))  // 4 (四舍五入)
11fmt.Println(math.Trunc(3.7))  // 3 (截断)
12
13// 三角函数(参数是弧度)
14rad := math.Pi / 4 // 45 度
15fmt.Println(math.Sin(rad)) // 0.707...
16fmt.Println(math.Cos(rad)) // 0.707...
17fmt.Println(math.Tan(rad)) // 1
18
19// 对数和指数
20fmt.Println(math.Log(10))    // 2.302... (自然对数)
21fmt.Println(math.Log10(100)) // 2 (常用对数)
22fmt.Println(math.Exp(2))     // 7.389... (e 的 2 次方)

计算两点距离

1func distance(x1, y1, x2, y2 float64) float64 {
2    dx := x2 - x1
3    dy := y2 - y1
4    return math.Hypot(dx, dy) // sqrt(x*x + y*y),更稳定
5}

要点

  • 三角函数参数是弧度,用 rad * math.Pi / 180 转换
  • math.Hypot 计算距离,避免溢出
  • 取整函数注意 Round 是四舍五入,Trunc 是截断

math/rand:随机数

rand 包提供了伪随机数生成功能。

初始化

1import (
2    "math/rand"
3    "time"
4)
5
6// 重要:必须设置种子
7rand.Seed(time.Now().UnixNano())

⚠️ 注意:不设置种子会导致每次运行结果相同。

生成随机数

 1// 随机整数 [0, n)
 2fmt.Println(rand.Intn(100))        // 0-99
 3fmt.Println(rand.Intn(10) + 1)     // 1-10
 4
 5// 随机浮点数 [0, 1)
 6fmt.Println(rand.Float64())        // 0.0 到 0.999...
 7
 8// 指定范围
 9min, max := 1, 100
10num := min + rand.Intn(max-min+1)  // [min, max]

随机选择和打乱

1colors := []string{"red", "green", "blue"}
2
3// 随机选择一个
4choice := colors[rand.Intn(len(colors))]
5
6// 打乱切片
7rand.Shuffle(len(colors), func(i, j int) {
8    colors[i], colors[j] = colors[j], colors[i]
9})

path/filepath:路径操作

path/filepath 提供了跨平台的文件路径操作。

路径拼接

1import "path/filepath"
2
3// 自动处理路径分隔符
4path := filepath.Join("dir", "subdir", "file.txt")
5// Windows: dir\subdir\file.txt
6// Linux/Mac: dir/subdir/file.txt

路径解析

 1// 获取文件名
 2fmt.Println(filepath.Base("/home/user/file.txt")) // file.txt
 3
 4// 获取目录
 5fmt.Println(filepath.Dir("/home/user/file.txt"))  // /home/user
 6
 7// 获取扩展名
 8fmt.Println(filepath.Ext("/home/user/file.txt"))  // .txt
 9
10// 分割路径和文件名
11dir, base := filepath.Split("/home/user/file.txt")

路径判断

1// 是否绝对路径
2fmt.Println(filepath.IsAbs("/home/user")) // true
3fmt.Println(filepath.IsAbs("user/file"))  // false
4
5// 路径匹配
6match, _ := filepath.Match("*.txt", "report.txt") // true
7match, _ = filepath.Match("file-?.txt", "file-1.txt") // true

路径清理

1// 清理路径(解析 . 和 ..)
2clean := filepath.Clean("/home/user/../user/./docs")
3fmt.Println(clean) // /home/user/docs

bytes:字节操作

bytes 包提供了字节切片的操作函数,类似于 strings 包。

基本操作

 1import "bytes"
 2
 3// 比较
 4a := []byte("hello")
 5b := []byte("world")
 6fmt.Println(bytes.Compare(a, b)) // -1 (a < b)
 7
 8// 包含
 9fmt.Println(bytes.Contains(a, []byte("lo"))) // true
10
11// 计数
12fmt.Println(bytes.Count(a, []byte("l"))) // 2

分割和连接

1// 分割
2data := []byte("a,b,c")
3parts := bytes.Split(data, []byte(","))
4// [a b c]
5
6// 连接
7result := bytes.Join(parts, []byte("-"))
8// a-b-c

构建字节数据

1// 使用 Buffer 高效构建
2var buf bytes.Buffer
3buf.WriteString("Hello")
4buf.WriteByte(',')
5buf.Write([]byte(" World!"))
6result := buf.Bytes()

替换

1data := []byte("hello")
2replaced := bytes.Replace(data, []byte("l"), []byte("L"), 1) // 替换1次
3// heLlo
4replaced = bytes.ReplaceAll(data, []byte("l"), []byte("L")) // 替换所有
5// heLLo

io:输入输出

io 包提供了基本的 I/O 接口。

常用接口

 1import "io"
 2
 3// Reader 接口
 4type Reader interface {
 5    Read(p []byte) (n int, err error)
 6}
 7
 8// Writer 接口
 9type Writer interface {
10    Write(p []byte) (n int, err error)
11}
12
13// Closer 接口
14type Closer interface {
15    Close() error
16}

实用函数

 1import (
 2    "io"
 3    "os"
 4    "strings"
 5)
 6
 7// 复制
 8src := strings.NewReader("Hello, World!")
 9dst := os.Stdout
10io.Copy(dst, src) // 输出: Hello, World!
11
12// 读取全部
13reader := strings.NewReader("Hello")
14data, _ := io.ReadAll(reader)
15fmt.Println(string(data)) // Hello
16
17// 限制读取
18reader = strings.NewReader("Hello, World!")
19limited := io.LimitReader(reader, 5)
20data, _ = io.ReadAll(limited)
21fmt.Println(string(data)) // Hello

完整示例

把前面的知识点串起来,看个完整的例子:

 1package main
 2
 3import (
 4    "encoding/json"
 5    "fmt"
 6    "os"
 7    "strings"
 8    "time"
 9)
10
11// 用户结构
12type User struct {
13    ID        int       `json:"id"`
14    Name      string    `json:"name"`
15    Email     string    `json:"email"`
16    CreatedAt time.Time `json:"created_at"`
17}
18
19// 格式化用户信息
20func (u User) String() string {
21    return fmt.Sprintf("User{ID: %d, Name: %s, Email: %s, Created: %s}",
22        u.ID, u.Name, u.Email, u.CreatedAt.Format("2006-01-02"))
23}
24
25// 保存用户到文件
26func saveUser(user User, filename string) error {
27    data, err := json.MarshalIndent(user, "", "  ")
28    if err != nil {
29        return fmt.Errorf("marshal error: %w", err)
30    }
31
32    err = os.WriteFile(filename, data, 0644)
33    if err != nil {
34        return fmt.Errorf("write file error: %w", err)
35    }
36
37    return nil
38}
39
40// 从文件加载用户
41func loadUser(filename string) (*User, error) {
42    data, err := os.ReadFile(filename)
43    if err != nil {
44        return nil, fmt.Errorf("read file error: %w", err)
45    }
46
47    var user User
48    err = json.Unmarshal(data, &user)
49    if err != nil {
50        return nil, fmt.Errorf("unmarshal error: %w", err)
51    }
52
53    return &user, nil
54}
55
56// 处理用户名
57func processName(name string) string {
58    // 去除空格并转为标题格式
59    name = strings.TrimSpace(name)
60    name = strings.Title(strings.ToLower(name))
61    return name
62}
63
64func main() {
65    // 创建用户
66    user := User{
67        ID:        1,
68        Name:      processName("  alice smith  "),
69        Email:     "alice@example.com",
70        CreatedAt: time.Now(),
71    }
72
73    // 打印用户信息
74    fmt.Println(user)
75
76    // 保存到文件
77    filename := "user.json"
78    err := saveUser(user, filename)
79    if err != nil {
80        fmt.Println("Save error:", err)
81        return
82    }
83    fmt.Println("User saved to", filename)
84
85    // 从文件加载
86    loadedUser, err := loadUser(filename)
87    if err != nil {
88        fmt.Println("Load error:", err)
89        return
90    }
91    fmt.Println("Loaded user:", loadedUser)
92
93    // 计算账户年龄
94    age := time.Since(loadedUser.CreatedAt)
95    fmt.Printf("Account age: %v\n", age.Round(time.Second))
96
97    // 清理
98    os.Remove(filename)
99}

这个例子展示了:

  • fmt 格式化输出
  • strings 字符串处理
  • time 时间操作
  • json 序列化和反序列化
  • os 文件操作
  • 错误处理和包装
  • 自定义 String 方法

老墨踩过的坑

坑 1:时间格式化用错模板

老墨刚从 Java 转 Go 时,习惯性地用 Java 的格式:

1now := time.Now()
2// ❌ 错误:用了常见的格式,但 Go 不认
3fmt.Println(now.Format("YYYY-MM-DD HH:mm:ss"))
4// 输出: YYYY-03-01 16:03:03 (完全不对)

问题:Go 的时间格式化必须用固定的参考时间 2006-01-02 15:04:05

正确做法

1// ✅ 正确:使用 Go 的参考时间
2fmt.Println(now.Format("2006-01-02 15:04:05"))
3// 输出: 2026-03-01 16:03:03
4
5// 助记:1月2号3点4分5秒06年

坑 2:JSON 序列化私有字段

1type User struct {
2    id   int    // ❌ 小写字母开头,不会被序列化
3    name string // ❌ 同样不会被序列化
4}
5
6user := User{id: 1, name: "Alice"}
7data, _ := json.Marshal(user)
8fmt.Println(string(data)) // 输出: {} (空对象!)

问题:只有导出字段(首字母大写)才会被序列化。

正确做法

1type User struct {
2    ID   int    `json:"id"`   // ✅ 大写字母开头
3    Name string `json:"name"` // ✅ 用标签指定 JSON 字段名
4}
5
6user := User{ID: 1, Name: "Alice"}
7data, _ := json.Marshal(user)
8fmt.Println(string(data)) // 输出: {"id":1,"name":"Alice"}

坑 3:字符串拼接性能问题

老墨曾经在循环中拼接大量字符串:

1// ❌ 性能差:每次拼接都创建新字符串
2var result string
3for i := 0; i < 10000; i++ {
4    result += "Go" // 非常慢!
5}

问题:字符串是不可变的,每次 += 都会创建新字符串,导致大量内存分配。

正确做法

1// ✅ 高效:使用 strings.Builder
2var builder strings.Builder
3for i := 0; i < 10000; i++ {
4    builder.WriteString("Go")
5}
6result := builder.String()

性能对比:10000 次拼接,Builder+ 快 100 倍以上!

坑 4:忘记检查 strconv 的错误

1// ❌ 危险:没有检查错误
2age, _ := strconv.Atoi("abc") // age = 0,但没有提示错误
3fmt.Println("Age:", age)      // 输出: Age: 0 (误导性的结果)

问题:转换失败时返回零值,如果不检查错误,会得到错误的结果。

正确做法

1// ✅ 正确:检查错误
2age, err := strconv.Atoi("abc")
3if err != nil {
4    fmt.Println("Invalid age:", err)
5    return
6}
7fmt.Println("Age:", age)

实战建议

1. 使用 fmt.Errorf 包装错误

 1// ❌ 不好:丢失上下文
 2func loadConfig(filename string) error {
 3    data, err := os.ReadFile(filename)
 4    if err != nil {
 5        return err // 不知道是哪个文件出错
 6    }
 7    // ...
 8}
 9
10// ✅ 好:添加上下文
11func loadConfig(filename string) error {
12    data, err := os.ReadFile(filename)
13    if err != nil {
14        return fmt.Errorf("load config %s: %w", filename, err)
15    }
16    // ...
17}

2. JSON 处理的最佳实践

 1type Config struct {
 2    Host    string        `json:"host"`
 3    Port    int           `json:"port"`
 4    Timeout time.Duration `json:"timeout,omitempty"` // 零值时省略
 5    Debug   bool          `json:"debug,omitempty"`
 6    Secret  string        `json:"-"` // 敏感信息不序列化
 7}
 8
 9// 自定义 JSON 序列化
10func (c Config) MarshalJSON() ([]byte, error) {
11    type Alias Config
12    return json.Marshal(&struct {
13        Timeout string `json:"timeout"` // 转换为可读格式
14        *Alias
15    }{
16        Timeout: c.Timeout.String(),
17        Alias:   (*Alias)(&c),
18    })
19}

3. 时间处理技巧

 1// 常用时间格式常量
 2const (
 3    DateFormat     = "2006-01-02"
 4    TimeFormat     = "15:04:05"
 5    DateTimeFormat = "2006-01-02 15:04:05"
 6    RFC3339Format  = time.RFC3339 // 2006-01-02T15:04:05Z07:00
 7)
 8
 9// 时区处理
10loc, _ := time.LoadLocation("Asia/Shanghai")
11now := time.Now().In(loc)
12
13// 时间范围判断
14func isBusinessHour(t time.Time) bool {
15    hour := t.Hour()
16    return hour >= 9 && hour < 18
17}
18
19// 计算两个日期之间的天数
20func daysBetween(start, end time.Time) int {
21    return int(end.Sub(start).Hours() / 24)
22}

4. 文件操作的安全模式

 1// 原子写入文件
 2func atomicWriteFile(filename string, data []byte) error {
 3    // 先写入临时文件
 4    tmpFile := filename + ".tmp"
 5    err := os.WriteFile(tmpFile, data, 0644)
 6    if err != nil {
 7        return err
 8    }
 9
10    // 重命名为目标文件(原子操作)
11    return os.Rename(tmpFile, filename)
12}
13
14// 安全读取文件
15func safeReadFile(filename string) ([]byte, error) {
16    // 检查文件是否存在
17    if _, err := os.Stat(filename); os.IsNotExist(err) {
18        return nil, fmt.Errorf("file not found: %s", filename)
19    }
20
21    // 限制文件大小(防止读取超大文件)
22    const maxSize = 10 * 1024 * 1024 // 10MB
23    info, err := os.Stat(filename)
24    if err != nil {
25        return nil, err
26    }
27    if info.Size() > maxSize {
28        return nil, fmt.Errorf("file too large: %d bytes", info.Size())
29    }
30
31    return os.ReadFile(filename)
32}

5. strings 包的高级用法

 1// 自定义分割函数
 2func splitByLength(s string, length int) []string {
 3    var result []string
 4    for i := 0; i < len(s); i += length {
 5        end := i + length
 6        if end > len(s) {
 7            end = len(s)
 8        }
 9        result = append(result, s[i:end])
10    }
11    return result
12}
13
14// 使用 strings.Builder 构建复杂字符串
15func buildHTML(title, content string) string {
16    var b strings.Builder
17    b.WriteString("<html><head><title>")
18    b.WriteString(title)
19    b.WriteString("</title></head><body>")
20    b.WriteString(content)
21    b.WriteString("</body></html>")
22    return b.String()
23}
24
25// 字符串清理
26func cleanString(s string) string {
27    s = strings.TrimSpace(s)
28    s = strings.ReplaceAll(s, "\r\n", "\n")
29    s = strings.ReplaceAll(s, "\r", "\n")
30    return s
31}

老墨总结

Go 标准库的 10 个关键点:

  1. fmt 格式化%v 万能占位符,Sprintf 返回字符串,Printf 直接输出
  2. strings 字符串:不可变字符串,频繁拼接用 Builder,丰富的操作函数
  3. strconv 转换:字符串和基本类型转换,Atoi/Itoa 最常用
  4. time 时间:格式化用 2006-01-02 15:04:05,时间计算用 AddSub
  5. json 处理:结构体标签控制序列化,只序列化导出字段
  6. os/io 文件ReadFile/WriteFile 简单操作,Open/Create 复杂操作
  7. sort 排序Ints/Strings 原地排序,SearchInts 二分查找
  8. math 数学Pi 常量,Pow/Sqrt 幂运算和开方,Hypot 计算距离
  9. math/rand 随机数:必须设置种子,Intn/Float64 生成随机数
  10. path/filepath 路径:跨平台路径操作,Join/Clean/Match

实战建议

  • 时间格式化记住参考时间 2006-01-02 15:04:05(1月2号3点4分5秒06年)
  • JSON 字段必须大写开头,用标签指定 JSON 名称
  • 频繁拼接字符串用 strings.Builder,性能提升 100 倍
  • strconv 转换必须检查错误,避免零值误导
  • 文件操作用 defer file.Close(),原子写入用临时文件+重命名
  • 随机数必须用 time.Now().UnixNano() 设置种子
  • 路径操作用 filepath.Join,自动处理不同系统的分隔符
  • 字节操作类似字符串,用 bytes.Buffer 高效构建

你最常用哪些标准库包?有没有发现什么好用的技巧?欢迎评论区聊聊。

极客老墨,继续折腾!

练习题

练习题 1:时间戳格式化(⭐)

编写一个函数,接收一个时间戳,返回格式化的日期字符串 “YYYY-MM-DD HH:MM:SS”。

要求:

  • 函数签名:func FormatTimestamp(timestamp int64) string
  • 处理时区(使用本地时区)
  • 测试边界情况(0、负数)

练习题 2:单词计数(⭐⭐)

实现一个函数,统计字符串中每个单词出现的次数,返回 map[string]int

要求:

  • 忽略大小写
  • 去除标点符号
  • 按出现次数降序排序

练习题 3:配置文件管理(⭐⭐)

定义一个 Config 结构体(包含 Host、Port、Timeout 字段),实现保存到 JSON 文件和从文件加载的功能。

要求:

  • 实现 SaveConfigLoadConfig 函数
  • 处理文件不存在的情况
  • 使用格式化的 JSON(带缩进)

练习题 4:文件行数统计(⭐⭐⭐)

编写一个函数,读取目录下所有 .txt 文件,统计总行数。

要求:

  • 递归遍历子目录
  • 跳过隐藏文件(以 . 开头)
  • 返回文件数和总行数

练习题 5:简单日志系统(⭐⭐⭐)

实现一个简单的日志函数,格式为 “[时间] [级别] 消息”,支持 INFO、WARN、ERROR 三个级别。

要求:

  • 定义 Logger 结构体
  • 实现 InfoWarnError 方法
  • 支持输出到文件和控制台

练习题 6:字符串拼接性能对比(⭐⭐⭐)

使用 strings.Builder 拼接 10000 个字符串,对比直接用 + 拼接的性能差异。

要求:

  • 使用 time.Now() 测量时间
  • 对比内存使用(提示:使用 runtime.MemStats
  • 输出性能对比报告

思考题

  1. 为什么 Go 的时间格式化要用 2006-01-02 15:04:05 这么奇怪的格式? 有什么设计考虑?
  2. strings.Builder 为什么比 + 拼接快那么多? 底层实现有什么不同?
  3. JSON 序列化时,omitempty 对哪些类型有效? 零值和空值有什么区别?
  4. 什么时候用 os.ReadFile,什么时候用 os.Open 它们的使用场景有什么不同?

快来评论区秀出你的想法,大家一起讨论!


你最常用哪些标准库包?有没有发现什么好用的技巧?欢迎评论区聊聊。

极客老墨,继续折腾!💪

如果有任何问题,欢迎在评论区留言或关注公众号「极客老墨」交流。

完整示例代码在 go-tutorial-code/11-stdlib


相关阅读