大家好,我是极客老墨。
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"
要点:
Atoi和Itoa是最常用的转换函数- 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}
要点:
Ints、Strings等函数原地排序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 个关键点:
- fmt 格式化:
%v万能占位符,Sprintf返回字符串,Printf直接输出 - strings 字符串:不可变字符串,频繁拼接用
Builder,丰富的操作函数 - strconv 转换:字符串和基本类型转换,
Atoi/Itoa最常用 - time 时间:格式化用
2006-01-02 15:04:05,时间计算用Add和Sub - json 处理:结构体标签控制序列化,只序列化导出字段
- os/io 文件:
ReadFile/WriteFile简单操作,Open/Create复杂操作 - sort 排序:
Ints/Strings原地排序,SearchInts二分查找 - math 数学:
Pi常量,Pow/Sqrt幂运算和开方,Hypot计算距离 - math/rand 随机数:必须设置种子,
Intn/Float64生成随机数 - 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 文件和从文件加载的功能。
要求:
- 实现
SaveConfig和LoadConfig函数 - 处理文件不存在的情况
- 使用格式化的 JSON(带缩进)
练习题 4:文件行数统计(⭐⭐⭐)
编写一个函数,读取目录下所有 .txt 文件,统计总行数。
要求:
- 递归遍历子目录
- 跳过隐藏文件(以
.开头) - 返回文件数和总行数
练习题 5:简单日志系统(⭐⭐⭐)
实现一个简单的日志函数,格式为 “[时间] [级别] 消息”,支持 INFO、WARN、ERROR 三个级别。
要求:
- 定义
Logger结构体 - 实现
Info、Warn、Error方法 - 支持输出到文件和控制台
练习题 6:字符串拼接性能对比(⭐⭐⭐)
使用 strings.Builder 拼接 10000 个字符串,对比直接用 + 拼接的性能差异。
要求:
- 使用
time.Now()测量时间 - 对比内存使用(提示:使用
runtime.MemStats) - 输出性能对比报告
思考题
- 为什么 Go 的时间格式化要用
2006-01-02 15:04:05这么奇怪的格式? 有什么设计考虑? strings.Builder为什么比+拼接快那么多? 底层实现有什么不同?- JSON 序列化时,
omitempty对哪些类型有效? 零值和空值有什么区别? - 什么时候用
os.ReadFile,什么时候用os.Open? 它们的使用场景有什么不同?
快来评论区秀出你的想法,大家一起讨论!
你最常用哪些标准库包?有没有发现什么好用的技巧?欢迎评论区聊聊。
极客老墨,继续折腾!💪
如果有任何问题,欢迎在评论区留言或关注公众号「极客老墨」交流。
完整示例代码在 go-tutorial-code/11-stdlib。