跳到主要内容

Go 标准库概览

Go 语言的标准库是其核心优势之一。与其他语言相比,Go 的标准库不仅功能全面,而且设计精良,API 风格统一。这意味着在大多数场景下,你不需要引入第三方依赖就能完成开发工作。

标准库的设计哲学

理解标准库的设计思想,能帮助你更好地使用它。

小而精的接口

Go 标准库的接口设计遵循"小接口"原则。一个接口通常只包含一两个方法,这让实现变得简单,也方便组合。最典型的例子就是 io 包中的接口:

// 只有一个方法的接口
type Reader interface {
Read(p []byte) (n int, err error)
}

type Writer interface {
Write(p []byte) (n int, err error)
}

// 通过组合构建更复杂的接口
type ReadWriter interface {
Reader
Writer
}

这种设计让你可以只实现需要的功能。如果你的类型只需要读取数据,就只实现 Reader 接口,不需要关心写入。

隐式实现

Go 没有显式的 implements 关键字。只要你的类型实现了接口定义的所有方法,它就自动满足该接口。这种设计让标准库的接口可以与你的自定义类型无缝配合:

// 定义自己的类型
type LogWriter struct{}

// 只需要实现 Write 方法,就自动满足了 io.Writer 接口
func (l *LogWriter) Write(p []byte) (n int, err error) {
log.Printf("写入 %d 字节: %s", len(p), string(p))
return len(p), nil
}

// 可以直接用于任何接受 io.Writer 的函数
func main() {
writer := &LogWriter{}
fmt.Fprintf(writer, "Hello, Go!") // 自动调用 Write 方法
}

零值可用

许多标准库类型在零值状态下就可以直接使用,无需初始化:

var buf bytes.Buffer  // 零值可直接使用
buf.WriteString("hello")

var mu sync.Mutex // 零值可直接使用
mu.Lock()
mu.Unlock()

核心接口:io 包

io 包是标准库的基础,它定义了 I/O 操作的核心接口。几乎所有涉及数据读写的地方都会用到这些接口。

Reader 和 Writer

这两个接口是 io 包的核心,分别代表数据的来源和目的地:

type Reader interface {
Read(p []byte) (n int, err error)
}

type Writer interface {
Write(p []byte) (n int, err error)
}

Read 方法从数据源读取数据到 p 中,返回实际读取的字节数和可能的错误。当数据读完时,返回 io.EOFWrite 方法将 p 中的数据写入目标,返回实际写入的字节数。

理解这两个接口的工作方式很重要:

func readFile(r io.Reader) ([]byte, error) {
// 使用 io.ReadAll 读取全部内容(Go 1.16+)
return io.ReadAll(r)
}

func copyData(dst io.Writer, src io.Reader) error {
// Copy 会一直读取直到遇到 EOF 或错误
_, err := io.Copy(dst, src)
return err
}

实现了 Reader 的类型

标准库中有大量类型实现了 io.Reader,这意味着它们都可以用于任何接受 io.Reader 的函数:

// *os.File - 文件
file, _ := os.Open("data.txt")
data, _ := io.ReadAll(file)

// *strings.Reader - 字符串
reader := strings.NewReader("hello world")
data, _ := io.ReadAll(reader)

// *bytes.Buffer - 字节缓冲区
buffer := bytes.NewBufferString("hello")
data, _ := io.ReadAll(buffer)

// *http.Response.Body - HTTP 响应体
resp, _ := http.Get("https://example.com")
data, _ := io.ReadAll(resp.Body)

// compress/gzip.Reader - gzip 压缩数据
gzReader, _ := gzip.NewReader(file)
data, _ := io.ReadAll(gzReader)

// crypto/cipher.StreamReader - 加密数据流
// ... 等等

这种统一的接口设计让你可以灵活组合各种数据源。比如,读取一个 gzip 压缩的 HTTP 响应:

resp, _ := http.Get("https://example.com/data.gz")
gzReader, _ := gzip.NewReader(resp.Body)
data, _ := io.ReadAll(gzReader)

组合接口

io 包通过接口组合定义了许多有用的组合接口:

// 读写组合
type ReadWriter interface {
Reader
Writer
}

// 带关闭的读写
type ReadWriteCloser interface {
Reader
Writer
Closer
}

// 带定位的读写
type ReadWriteSeeker interface {
Reader
Writer
Seeker
}

实用函数

io 包提供了一些实用的辅助函数:

// Copy: 从 src 复制到 dst
io.Copy(dst, src)

// CopyN: 复制指定字节数
io.CopyN(dst, src, 1024)

// ReadAll: 读取全部内容
data, _ := io.ReadAll(reader)

// WriteString: 写入字符串
io.WriteString(writer, "hello")

// Pipe: 创建同步管道
r, w := io.Pipe()
go func() {
w.Write([]byte("hello"))
w.Close()
}()
data, _ := io.ReadAll(r)

多路复用

io 包提供了几种数据分发和收集的工具:

// MultiReader: 顺序读取多个 Reader
r1 := strings.NewReader("first ")
r2 := strings.NewReader("second ")
r3 := strings.NewReader("third")
mr := io.MultiReader(r1, r2, r3)
data, _ := io.ReadAll(mr) // "first second third"

// MultiWriter: 同时写入多个 Writer
var buf1, buf2 bytes.Buffer
mw := io.MultiWriter(&buf1, &buf2)
mw.Write([]byte("hello"))
// buf1 和 buf2 都包含 "hello"

// TeeReader: 读取的同时写入
var buf bytes.Buffer
r := strings.NewReader("hello")
tr := io.TeeReader(r, &buf)
data, _ := io.ReadAll(tr)
// data 和 buf 都包含 "hello"

TeeReader 特别有用,它可以在读取数据的同时将数据写入另一个 Writer,比如用于记录请求日志:

func logRequestBody(body io.Reader) (io.Reader, []byte) {
var buf bytes.Buffer
tr := io.TeeReader(body, &buf)
data, _ := io.ReadAll(tr)
return &buf, data
}

缓冲 I/O:bufio 包

bufio 包在 io 包的基础上添加了缓冲功能,可以显著提高 I/O 操作的效率。

为什么需要缓冲

每次调用系统调用读取数据都有开销。如果每次只读取一个字节,频繁的系统调用会严重影响性能。缓冲的做法是先读取一大块数据到内存,然后从内存中逐个返回,减少系统调用次数。

// 不使用缓冲:每次 Read 都可能触发系统调用
file, _ := os.Open("large.txt")
buffer := make([]byte, 1)
for {
_, err := file.Read(buffer)
if err == io.EOF {
break
}
// 处理每个字节...
}

// 使用缓冲:减少系统调用次数
file, _ := os.Open("large.txt")
reader := bufio.NewReader(file)
for {
b, err := reader.ReadByte()
if err == io.EOF {
break
}
// 处理每个字节...
}

Scanner:逐行读取

bufio.Scanner 是逐行读取文本的最佳选择,比传统的 ReadString 更方便:

func readLines(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}

return scanner.Err()
}

Scanner 的优势在于它会自动处理缓冲和行分割,而且错误处理更简洁。默认按行分割,但也可以自定义:

// 按单词分割
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
word := scanner.Text()
fmt.Println(word)
}

// 按固定宽度分割
func fixedWidthSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
if len(data) < 10 {
return 0, nil, nil // 需要更多数据
}
return 10, data[:10], nil
}
scanner.Split(fixedWidthSplit)

Writer:缓冲写入

写入时使用缓冲同样重要:

func writeLines(filename string, lines []string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()

writer := bufio.NewWriter(file)
for _, line := range lines {
writer.WriteString(line)
writer.WriteByte('\n')
}

// 必须调用 Flush 确保所有数据写入
return writer.Flush()
}

注意,使用 bufio.Writer 时必须调用 Flush() 方法,否则缓冲区中的数据可能不会被写入目标。

字节处理:bytes 包

bytes 包提供了操作字节切片的工具,类似于 strings 包对字符串的操作。

Buffer 类型

bytes.Buffer 是一个可变大小的字节缓冲区,实现了 io.Readerio.Writerio.StringWriter 接口:

var buf bytes.Buffer

// 写入数据
buf.WriteString("hello")
buf.WriteByte(' ')
buf.Write([]byte("world"))

// 读取数据
data := buf.Bytes() // 获取底层数据(不复制)
str := buf.String() // 转换为字符串

// 重置缓冲区,复用内存
buf.Reset()

bytes.Buffer 特别适合需要多次拼接字符串的场景,比使用 +fmt.Sprintf 高效得多:

// 不推荐:每次拼接都创建新字符串
result := ""
for i := 0; i < 1000; i++ {
result += fmt.Sprintf("item %d\n", i)
}

// 推荐:使用 bytes.Buffer
var buf bytes.Buffer
for i := 0; i < 1000; i++ {
fmt.Fprintf(&buf, "item %d\n", i)
}
result := buf.String()

字节切片操作

bytes 包提供了许多操作字节切片的函数,与 strings 包的函数相对应:

data := []byte("Hello, World!")

// 查找
bytes.Contains(data, []byte("World")) // true
bytes.Index(data, []byte(",")) // 5
bytes.HasPrefix(data, []byte("Hello")) // true

// 转换
bytes.ToUpper(data) // []byte("HELLO, WORLD!")
bytes.ToLower(data) // []byte("hello, world!")
bytes.Title(data) // []byte("Hello, World!")

// 分割和连接
parts := bytes.Split(data, []byte(" ")) // [][]byte{...}
joined := bytes.Join(parts, []byte("-")) // []byte("Hello,-World!")

// 替换和修剪
bytes.Replace(data, []byte("World"), []byte("Go"), 1)
bytes.TrimSpace([]byte(" hello ")) // []byte("hello")

// 比较
bytes.Equal([]byte("a"), []byte("a")) // true
bytes.Compare([]byte("a"), []byte("b")) // -1

字符串处理:strings 包

strings 包提供了丰富的字符串操作函数,是日常开发中最常用的包之一。

查找和判断

str := "Hello, World!"

// 包含判断
strings.Contains(str, "World") // true
strings.ContainsAny(str, "abc") // true(包含 a、b、c 中任一字符)
strings.ContainsRune(str, '世') // false

// 前缀后缀
strings.HasPrefix(str, "Hello") // true
strings.HasSuffix(str, "!") // true

// 查找位置
strings.Index(str, "World") // 7
strings.LastIndex(str, "l") // 10
strings.IndexAny(str, "aeiou") // 1(第一个元音字母位置)

// 计数
strings.Count("hello", "l") // 2

转换和替换

str := "Hello, World!"

// 大小写转换
strings.ToUpper(str) // "HELLO, WORLD!"
strings.ToLower(str) // "hello, world!"
strings.Title("hello world") // "Hello World"

// 替换
strings.Replace(str, "World", "Go", 1) // 替换 1 次
strings.ReplaceAll(str, "l", "L") // 替换所有

// 修剪
strings.TrimSpace(" hello ") // "hello"
strings.Trim("!!hello!!", "!") // "hello"
strings.TrimPrefix("hello.txt", "hello.") // "txt"
strings.TrimSuffix("hello.txt", ".txt") // "hello"

分割和连接

// 分割
parts := strings.Split("a,b,c", ",") // ["a", "b", "c"]
fields := strings.Fields("a b\tc\n") // ["a", "b", "c"](按空白分割)

// 限制分割次数
parts = strings.SplitN("a,b,c", ",", 2) // ["a", "b,c"]

// 分割后保留分隔符
parts = strings.SplitAfter("a,b,c", ",") // ["a,", "b,", "c"]

// 连接
strings.Join([]string{"a", "b", "c"}, "-") // "a-b-c"
strings.Repeat("ab", 3) // "ababab"

Builder 类型

Go 1.10 引入了 strings.Builder,专门用于高效构建字符串:

var sb strings.Builder
sb.WriteString("Hello")
sb.WriteByte(',')
sb.WriteRune('世')
sb.WriteString("界")

result := sb.String() // "Hello,世界"

// 重置复用
sb.Reset()
sb.WriteString("new content")

strings.Builderbytes.Buffer 类似,但专门为字符串优化,且不允许读取已写入的内容。

格式化输出:fmt 包

fmt 包实现了格式化的输入输出,类似于 C 语言的 printf 和 scanf。

打印函数

// 基本打印
fmt.Print("Hello") // 不换行
fmt.Println("Hello") // 换行
fmt.Printf("Hello %s", "World") // 格式化

// 打印到字符串
s := fmt.Sprint("Hello")
s = fmt.Sprintf("Hello %s", "World")
s = fmt.Sprintln("Hello")

// 打印到 io.Writer
fmt.Fprint(os.Stdout, "Hello")
fmt.Fprintf(file, "Name: %s", name)
fmt.Fprintln(writer, "Hello")

格式化动词

// 通用动词
fmt.Printf("%v", data) // 默认格式
fmt.Printf("%+v", struct) // 结构体添加字段名
fmt.Printf("%#v", data) // Go 语法表示
fmt.Printf("%T", data) // 类型
fmt.Printf("%%") // 百分号

// 整数
fmt.Printf("%b", 15) // 二进制: 1111
fmt.Printf("%d", 15) // 十进制: 15
fmt.Printf("%o", 15) // 八进制: 17
fmt.Printf("%x", 15) // 十六进制: f
fmt.Printf("%X", 15) // 十六进制: F

// 浮点数
fmt.Printf("%f", 3.14) // 浮点数: 3.140000
fmt.Printf("%.2f", 3.14159) // 保留2位: 3.14
fmt.Printf("%e", 3.14) // 科学计数法: 3.140000e+00
fmt.Printf("%g", 3.14) // 自动选择: 3.14

// 字符串
fmt.Printf("%s", "hello") // 字符串: hello
fmt.Printf("%q", "hello") // 带引号: "hello"
fmt.Printf("%10s", "hi") // 宽度10: hi
fmt.Printf("%-10s", "hi") // 左对齐: hi
fmt.Printf("%x", "hello") // 十六进制: 68656c6c6f

// 其他
fmt.Printf("%t", true) // 布尔: true
fmt.Printf("%p", &x) // 指针: 0xc0000...
fmt.Printf("%c", 65) // Unicode 字符: A

宽度和精度

// 宽度:最小输出宽度
fmt.Printf("|%5d|", 42) // | 42|
fmt.Printf("|%-5d|", 42) // |42 |
fmt.Printf("|%05d|", 42) // |00042|

// 精度:浮点数小数位数或字符串最大长度
fmt.Printf("|%.2f|", 3.14159) // |3.14|
fmt.Printf("|%.5s|", "hello world") // |hello|
fmt.Printf("|%8.2f|", 3.14159) // | 3.14|

扫描输入

var name string
var age int

// 从标准输入读取
fmt.Scan(&name, &age) // 空格分隔
fmt.Scanf("%s %d", &name, &age) // 格式化读取

// 从字符串读取
fmt.Sscan("张三 25", &name, &age)

// 读取整行
reader := bufio.NewReader(os.Stdin)
line, _ := reader.ReadString('\n')

时间处理:time 包

time 包提供了时间的测量、解析和格式化功能。

获取时间

// 当前时间
now := time.Now()

// 指定时间
t := time.Date(2024, 1, 1, 12, 0, 0, 0, time.Local)

// Unix 时间戳
timestamp := now.Unix() // 秒
nano := now.UnixNano() // 纳秒

// 从时间戳创建
t = time.Unix(1704067200, 0)

格式化和解析

Go 使用一个特殊的参考时间来定义格式:2006-01-02 15:04:05 MST,每个数字都是固定的:

t := time.Now()

// 格式化
s := t.Format("2006-01-02 15:04:05")
fmt.Println(s) // 2024-01-15 10:30:00

// 常用格式
fmt.Println(t.Format("2006-01-02")) // 2024-01-15
fmt.Println(t.Format("15:04:05")) // 10:30:00
fmt.Println(t.Format("2006-01-02T15:04:05Z07:00")) // ISO 8601

// 解析
t, err := time.Parse("2006-01-02", "2024-01-15")
t, err = time.ParseInLocation("2006-01-02", "2024-01-15", time.Local)

时间运算

now := time.Now()

// 加减时间
later := now.Add(24 * time.Hour)
before := now.Add(-1 * time.Hour)

// 时间差
diff := later.Sub(now) // 24h0m0s

// 比较
now.Before(later) // true
now.After(later) // false
now.Equal(later) // false

Duration

time.Duration 表示时间间隔:

// 预定义的 Duration
const (
Nanosecond time.Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)

// 使用
duration := 2 * time.Hour + 30 * time.Minute
fmt.Println(duration) // 2h30m0s
fmt.Println(duration.Minutes()) // 150
fmt.Println(duration.Seconds()) // 9000
fmt.Println(duration.Milliseconds()) // 9000000

定时器

// Timer:一次性定时
timer := time.NewTimer(5 * time.Second)
<-timer.C // 阻塞直到时间到

// 可以提前停止
stopped := timer.Stop()
if stopped {
fmt.Println("定时器已取消")
}

// 可以重置
timer.Reset(5 * time.Second)

// Ticker:周期性定时
ticker := time.NewTicker(1 * time.Second)
for t := range ticker.C {
fmt.Println("Tick at", t)
}
ticker.Stop()

// After:简化的一次性定时
select {
case <-time.After(5 * time.Second):
fmt.Println("超时")
case msg := <-ch:
fmt.Println("收到:", msg)
}

// Sleep:简单延迟
time.Sleep(1 * time.Second)

操作系统交互:os 包

os 包提供了与操作系统交互的功能,包括文件操作、环境变量、进程管理等。

文件操作

// 打开文件(只读)
file, err := os.Open("test.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()

// 创建文件(可写)
file, err := os.Create("new.txt")

// 打开或创建
file, err := os.OpenFile("file.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)

// 快捷读取(Go 1.16+)
data, err := os.ReadFile("test.txt")

// 快捷写入(Go 1.16+)
err := os.WriteFile("test.txt", []byte("hello"), 0644)

// 文件信息
info, err := os.Stat("test.txt")
fmt.Println(info.Name()) // 文件名
fmt.Println(info.Size()) // 大小
fmt.Println(info.IsDir()) // 是否目录
fmt.Println(info.ModTime()) // 修改时间

// 判断文件是否存在
_, err := os.Stat("test.txt")
if os.IsNotExist(err) {
fmt.Println("文件不存在")
}

// 删除文件
err := os.Remove("test.txt")

// 重命名
err := os.Rename("old.txt", "new.txt")

os.Root:目录隔离操作(Go 1.24+)

Go 1.24 引入了 os.Root 类型,它提供了一种在特定目录下执行文件系统操作的安全方式,确保所有操作都不会逃逸到指定目录之外。这是 Go 官方为解决**路径遍历漏洞(Path Traversal)**而设计的核心安全机制。

为什么需要 os.Root?

路径遍历漏洞是一类常见的安全问题,攻击者可以欺骗程序打开预期之外的文件。常见的攻击方式包括:

1. 目录遍历攻击:攻击者使用 .. 逃逸预期目录

// 危险:攻击者可能控制 filename,导致访问 /etc/passwd
f, err := os.Open(filepath.Join(trustedLocation, "../../../../etc/passwd"))

2. 符号链接攻击:攻击者创建符号链接指向敏感文件

// 危险:攻击者可能将 /home/user/.config 链接到其他用户的目录
err := os.WriteFile("/home/user/.config/foo", config, 0666)

3. Windows 特殊名称:Windows 有特殊的设备名称

// 危险:在 Windows 上会打印到控制台而不是创建文件
f, err := os.Create(filepath.Join(trustedLocation, "CONOUT$"))

4. TOCTOU 竞态条件:检查与使用之间的时间窗口

// 危险:验证后、使用前,攻击者可能修改路径
cleaned, _ := filepath.EvalSymlinks(unsafePath)
if filepath.IsLocal(cleaned) {
// 攻击者在此期间替换路径
f, err := os.Open(cleaned) // 可能打开错误的文件
}

os.Root 的安全机制

os.Root 通过以下机制防御上述攻击:

import "os"

func safeFileOperations() error {
// 创建一个 Root,限定在 "./data" 目录下
root, err := os.OpenRoot("./data")
if err != nil {
return err
}
defer root.Close()

// 在 Root 内创建文件(路径相对于 Root 目录)
f, err := root.Create("user/file.txt")
if err != nil {
return err
}
defer f.Close()

f.WriteString("Hello, World!")

// 读取文件
f, err = root.Open("user/file.txt")
if err != nil {
return err
}
data, _ := io.ReadAll(f)
fmt.Println(string(data))

// 列出目录内容
entries, err := root.ReadDir(".")
for _, entry := range entries {
fmt.Println(entry.Name())
}

// 创建目录
err = root.Mkdir("newdir", 0755)

// 删除文件
err = root.Remove("user/file.txt")

return nil
}

阻止目录遍历

os.Root 会阻止任何逃逸出 Root 目录的操作:

root, _ := os.OpenRoot("./data")

// 以下操作都会失败,因为它们试图逃逸出 Root 目录
_, err := root.Open("../etc/passwd") // 错误:路径逃逸
_, err = root.Open("../../secret") // 错误:路径逃逸
_, err = root.Create("/etc/malicious") // 错误:绝对路径不允许

允许安全的相对路径

// 允许不逃逸的相对路径
root.Open("a/../b") // 允许:解析后仍在 Root 内
root.Open("subdir/./file") // 允许:规范化后仍在 Root 内

防御符号链接攻击

os.Root 会跟踪符号链接,但阻止指向 Root 之外的链接:

// 假设 subdir 是一个指向 /etc 的符号链接
_, err := root.Open("subdir/passwd") // 错误:符号链接指向 Root 之外

平台实现细节

Unix 系统

  • 使用 openat 系列系统调用实现
  • Root 包含一个文件描述符,引用根目录
  • 目录重命名或删除后,Root 仍会跟踪原目录
  • 不限制挂载点遍历(如 Linux bind mount)

Windows 系统

  • Root 打开一个目录句柄
  • 句柄会阻止目录被重命名或删除
  • 阻止访问 Windows 保留设备名(如 NULCOM1

何时使用 os.Root

应该使用

  • 在固定目录中打开用户提供的文件名
  • 文件名可能来自不可信来源
  • 操作不应该访问目录之外的文件

不应该使用

  • 用户明确指定了完整的输出路径(如命令行工具)
  • 需要访问文件系统任意位置
// 应该使用 os.Root 的场景
func serveDownload(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Query().Get("file") // 用户输入
root, _ := os.OpenRoot("/var/www/downloads")
f, err := root.Open(filename) // 安全:无法逃逸
// ...
}

// 不应该使用 os.Root 的场景
func saveOutput(outputPath string, data []byte) error {
// outputPath 是用户明确指定的,可能指向任何位置
return os.WriteFile(outputPath, data, 0644)
}

简化用法:os.OpenInRoot

如果只需要打开单个文件,可以使用更简单的 os.OpenInRoot 函数:

// 打开单个文件(不需要创建 Root 对象)
f, err := os.OpenInRoot("/var/www/downloads", userProvidedFilename)

这等价于:

root, _ := os.OpenRoot("/var/www/downloads")
f, err := root.Open(userProvidedFilename)

适用场景

  • Web 服务器提供文件下载,需要安全地处理用户提供的路径
  • 沙箱环境中的文件操作
  • 插件系统的文件访问隔离
  • 用户上传文件的存储管理
  • 解压缩工具写入输出目录

os.Root 支持的方法

方法说明
Create(name string)创建文件
Open(name string)打开文件
OpenFile(name string, flag int, perm FileMode)打开文件(指定模式和权限)
Mkdir(name string, perm FileMode)创建目录
MkdirAll(name string, perm FileMode)创建多级目录
Remove(name string)删除文件或空目录
RemoveAll(name string)删除目录及其内容
Rename(oldname, newname string)重命名
Stat(name string)获取文件信息
Lstat(name string)获取文件信息(不跟随符号链接)
ReadDir(name string)读取目录内容
Chmod(name string, mode FileMode)修改权限
Chown(name string, uid, gid int)修改所有者
Chtimes(name string, atime, mtime time.Time)修改访问和修改时间

目录操作

// 创建目录
err := os.Mkdir("dir", 0755) // 创建单个目录
err := os.MkdirAll("a/b/c", 0755) // 创建多级目录

// 删除目录
err := os.Remove("dir") // 删除空目录
err := os.RemoveAll("dir") // 删除目录及内容

// 读取目录
entries, err := os.ReadDir(".")
for _, entry := range entries {
fmt.Println(entry.Name(), entry.IsDir())
}

// 获取当前目录
dir, err := os.Getwd()

// 改变当前目录
err := os.Chdir("/tmp")

环境变量

// 获取环境变量
home := os.Getenv("HOME")
path := os.Getenv("PATH")

// 获取所有环境变量
for _, env := range os.Environ() {
fmt.Println(env)
}

// 设置环境变量
os.Setenv("MY_VAR", "value")

// 查找环境变量
value, exists := os.LookupEnv("MY_VAR")
if !exists {
fmt.Println("环境变量不存在")
}

// 取消设置
os.Unsetenv("MY_VAR")

进程控制

// 获取进程 ID
fmt.Println(os.Getpid())

// 获取父进程 ID
fmt.Println(os.Getppid())

// 退出程序
os.Exit(0) // 正常退出
os.Exit(1) // 异常退出

// 执行外部命令
cmd := exec.Command("ls", "-la")
output, err := cmd.Output()

数学运算:math 包

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

import "math"

// 数学常量
math.Pi // π ≈ 3.14159
math.E // e ≈ 2.71828
math.Phi // 黄金比例 ≈ 1.61803

// 基本运算
math.Abs(-5) // 5 - 绝对值
math.Sqrt(16) // 4 - 平方根
math.Pow(2, 3) // 8 - 幂运算
math.Mod(10, 3) // 1 - 取模

// 取整
math.Floor(3.7) // 3 - 向下取整
math.Ceil(3.2) // 4 - 向上取整
math.Round(3.5) // 4 - 四舍五入
math.Trunc(3.7) // 3 - 截断小数

// 三角函数
math.Sin(math.Pi / 2) // 1
math.Cos(0) // 1
math.Tan(math.Pi / 4) // 1

// 对数和指数
math.Log(math.E) // 1 - 自然对数
math.Log10(100) // 2 - 以 10 为底
math.Log2(8) // 3 - 以 2 为底
math.Exp(1) // e

// 最值
math.Max(1.5, 2.5) // 2.5
math.Min(1.5, 2.5) // 1.5

// 符号判断
math.Signbit(-5) // true - 是否为负
math.Copysign(5, -1) // -5 - 复制符号

// 特殊值判断
math.IsNaN(math.NaN()) // true
math.IsInf(math.Inf(1), 1) // true
math.IsInf(math.Inf(-1), -1) // true

Go 1.21+ 新增内置函数

Go 1.21 引入了三个新的内置函数:minmaxclear,这些函数让常见的操作变得更加简洁。

min 和 max 函数

minmax 函数用于计算多个值中的最小值和最大值:

// 基本用法:比较两个值
minVal := min(3, 5) // 3
maxVal := max(3, 5) // 5

// 比较多个值
minVal = min(1, 2, 3, 4, 5) // 1
maxVal = max(1, 2, 3, 4, 5) // 5

// 支持所有有序类型
minFloat := min(3.14, 2.71) // 2.71
minStr := min("apple", "banana") // "apple"(按字典序)

// 实际应用:限制值在某个范围内
func clamp(value, minVal, maxVal int) int {
return min(max(value, minVal), maxVal)
}

// 例如:将用户输入限制在 0-100 之间
userInput := 150
normalized := clamp(userInput, 0, 100) // 100

// 在切片中找最小/最大值
nums := []int{5, 2, 8, 1, 9}
smallest := nums[0]
largest := nums[0]
for _, n := range nums[1:] {
smallest = min(smallest, n)
largest = max(largest, n)
}

类型约束minmax 支持所有支持比较运算符的类型,包括:

  • 整数类型:intint8int16int32int64 及其无符号版本
  • 浮点类型:float32float64
  • 字符串类型:string

clear 函数

clear 函数用于清空 map 或切片中的所有元素:

// 清空 map
m := map[string]int{"a": 1, "b": 2, "c": 3}
clear(m)
fmt.Println(len(m)) // 0

// 清空切片(将元素置为零值)
s := []int{1, 2, 3, 4, 5}
clear(s)
fmt.Println(s) // [0 0 0 0 0]
fmt.Println(len(s)) // 5(长度不变,元素被清零)

// 实际应用:复用切片避免重新分配
var buffer []byte
for i := 0; i < 1000; i++ {
buffer = buffer[:0] // 或者 clear(buffer)
// 使用 buffer 进行操作...
processBuffer(buffer)
}

clear 与切片重置的区别

  • s = s[:0]:长度变为 0,但元素仍在内存中
  • clear(s):长度不变,但所有元素被置为零值

结构化日志:log/slog 包

Go 1.21 引入了 log/slog 包,提供结构化的日志记录功能。相比传统的 log 包,slog 支持键值对格式的日志,便于日志的解析、过滤和搜索。

基本使用

import "log/slog"

// 使用默认 logger
slog.Info("用户登录", "user_id", 12345, "ip", "192.168.1.1")
slog.Error("数据库连接失败", "err", err, "host", "localhost")
slog.Warn("内存使用率过高", "usage", 85.5)
slog.Debug("调试信息", "key", "value")

// 输出示例:
// 2024/01/15 10:30:00 INFO 用户登录 user_id=12345 ip=192.168.1.1

创建自定义 Logger

import (
"log/slog"
"os"
)

// 创建文本格式的 logger
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
logger.Info("hello", "count", 3)
// 输出:time=2024-01-15T10:30:00.000Z level=INFO msg=hello count=3

// 创建 JSON 格式的 logger(推荐用于生产环境)
jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
jsonLogger.Info("request received", "method", "GET", "path", "/api/users")
// 输出:{"time":"2024-01-15T10:30:00Z","level":"INFO","msg":"request received","method":"GET","path":"/api/users"}

// 设置为默认 logger
slog.SetDefault(jsonLogger)

日志级别

slog 定义了四个日志级别,从低到高:

const (
LevelDebug Level = -4
LevelInfo Level = 0
LevelWarn Level = 4
LevelError Level = 8
)

// 设置最低日志级别
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo, // 只记录 Info 及以上级别
})

// 动态调整日志级别
var programLevel = new(slog.LevelVar) // 默认为 Info
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: programLevel,
})
logger := slog.New(handler)

// 需要调试时动态调整为 Debug 级别
programLevel.Set(slog.LevelDebug)

使用属性(Attr)

使用 slog.Attr 类型可以创建结构化的属性:

// 直接使用键值对
slog.Info("用户操作", "user_id", 123, "action", "login")

// 使用 Attr 类型
slog.Info("用户操作",
slog.Int("user_id", 123),
slog.String("action", "login"),
slog.Duration("elapsed", time.Second),
)

// 分组属性
slog.Info("请求处理完成",
slog.Group("request",
slog.String("method", "GET"),
slog.String("path", "/api/users"),
),
slog.Int("status", 200),
slog.Duration("latency", 50*time.Millisecond),
)
// JSON 输出:{"time":"...","level":"INFO","msg":"请求处理完成","request":{"method":"GET","path":"/api/users"},"status":200,"latency":50000000}

添加上下文信息

使用 With 方法创建带有预设属性的 logger:

// 创建带有请求 ID 的 logger
requestLogger := logger.With("request_id", "abc-123")
requestLogger.Info("处理开始")
requestLogger.Info("处理完成")

// 在 HTTP 中间件中使用
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := generateRequestID()
logger := slog.With(
"request_id", requestID,
"method", r.Method,
"path", r.URL.Path,
)

start := time.Now()
next.ServeHTTP(w, r)

logger.Info("请求完成", "duration", time.Since(start))
})
}

自定义类型日志

通过实现 slog.LogValuer 接口,可以自定义类型的日志输出:

type User struct {
ID int
Name string
// 敏感字段,不应出现在日志中
Password string
}

func (u User) LogValue() slog.Value {
return slog.GroupValue(
slog.Int("id", u.ID),
slog.String("name", u.Name),
// 故意省略 Password 字段
)
}

user := User{ID: 1, Name: "张三", Password: "secret"}
slog.Info("用户登录", "user", user)
// 输出不会包含 Password 字段

迭代器:iter 包(Go 1.23+)

Go 1.23 引入了迭代器(Iterator)作为语言的新特性,这是 Go 语言的一次重要增强。迭代器允许你使用 for range 循环遍历自定义的序列,使代码更加简洁和统一。

什么是迭代器?

迭代器是一种函数,它可以逐个产生序列中的值。在 Go 1.23 中,迭代器函数的定义如下:

// 迭代器函数类型
type Seq[V any] func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K, V) bool)

迭代器函数接受一个 yield 回调函数作为参数。每次调用 yield 会产生一个迭代值,如果 yield 返回 false,表示调用者希望停止迭代。

基本用法

Go 1.23 的 for range 循环现在可以直接遍历迭代器函数:

package main

import (
"fmt"
"slices"
)

func main() {
// 使用迭代器遍历切片
numbers := []int{1, 2, 3, 4, 5}

// slices.Values 返回切片元素的迭代器
for v := range slices.Values(numbers) {
fmt.Println(v)
}

// slices.All 返回索引和值的迭代器
for i, v := range slices.All(numbers) {
fmt.Printf("索引 %d: 值 %d\n", i, v)
}
}

自定义迭代器

你可以为自己的类型定义迭代器,使其能够用于 for range 循环:

package main

import "fmt"

// 自定义集合类型
type IntSet struct {
values map[int]bool
}

func NewIntSet(vals ...int) *IntSet {
s := &IntSet{values: make(map[int]bool)}
for _, v := range vals {
s.values[v] = true
}
return s
}

// 定义迭代器方法:返回一个迭代器函数
func (s *IntSet) All() func(func(int) bool) {
return func(yield func(int) bool) {
for v := range s.values {
if !yield(v) {
return
}
}
}
}

func main() {
set := NewIntSet(1, 2, 3, 4, 5)

// 使用 for range 遍历自定义类型
for v := range set.All() {
fmt.Println(v)
}
}

迭代器的执行机制

理解迭代器的执行机制对于正确使用它们很重要:

func main() {
// 迭代器执行流程示意
for v := range countTo(3) {
fmt.Println("收到:", v)
}
fmt.Println("循环结束")
}

// 定义一个简单的计数迭代器
func countTo(n int) func(func(int) bool) {
return func(yield func(int) bool) {
for i := 1; i <= n; i++ {
fmt.Printf("产生: %d\n", i)
if !yield(i) { // 如果 yield 返回 false,停止迭代
fmt.Println("迭代被中断")
return
}
}
fmt.Println("迭代完成")
}
}

// 输出:
// 产生: 1
// 收到: 1
// 产生: 2
// 收到: 2
// 产生: 3
// 收到: 3
// 迭代完成
// 循环结束

提前终止迭代

当循环提前退出(如使用 breakreturn)时,迭代器会被通知:

func main() {
for v := range countTo(10) {
fmt.Println(v)
if v == 3 {
break // 提前终止,yield 返回 false
}
}
fmt.Println("循环提前结束")
}

// 输出:
// 产生: 1
// 1
// 产生: 2
// 2
// 产生: 3
// 3
// 迭代被中断
// 循环提前结束

Pull 迭代器

除了"推"模式的迭代器(通过 yield 推送值),Go 还提供了"拉"模式的迭代器:

import "iter"

// 将推迭代器转换为拉迭代器
func pullExample() {
numbers := []int{1, 2, 3, 4, 5}

// iter.Pull 将迭代器转换为 next 函数和 stop 函数
next, stop := iter.Pull(slices.Values(numbers))
defer stop() // 确保释放资源

for {
v, ok := next()
if !ok {
break
}
fmt.Println(v)
}
}

拉迭代器适用于需要手动控制迭代的场景,如:

  • 在迭代过程中需要做复杂的状态管理
  • 需要跨多个函数传递迭代状态
  • 与其他语言的迭代器模式对接

迭代器的优势

  1. 统一的遍历接口:所有可迭代类型都可以使用相同的 for range 语法
  2. 惰性求值:只在需要时计算下一个值,节省内存
  3. 可组合性:可以轻松组合多个迭代操作
  4. 资源安全:提前退出时会正确清理资源
// 组合迭代操作示例
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

// 链式操作:过滤 -> 转换 -> 收集
evens := slices.Collect(filterEven(slices.Values(numbers)))
fmt.Println(evens) // [2, 4, 6, 8, 10]
}

// 定义过滤迭代器
func filterEven(seq iter.Seq[int]) iter.Seq[int] {
return func(yield func(int) bool) {
for v := range seq {
if v%2 == 0 {
if !yield(v) {
return
}
}
}
}
}

iter 包概览

iter 包提供了迭代器的核心定义:

import "iter"

// 核心类型
type Seq[V any] func(yield func(V) bool) // 单值迭代器
type Seq2[K, V any] func(yield func(K, V) bool) // 键值对迭代器

// Pull 函数:将推迭代器转换为拉迭代器
func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func())
func Pull2[K, V any](seq Seq2[K, V]) (next func() (K, V, bool), stop func())

切片操作:slices 包

Go 1.21 引入了 slices 包,提供了丰富的泛型切片操作函数。这些函数让切片操作变得更加简洁和安全。Go 1.23 又为该包添加了迭代器相关函数。

查找和判断

import "slices"

nums := []int{3, 1, 4, 1, 5, 9, 2, 6}

// 检查元素是否存在
slices.Contains(nums, 5) // true
slices.Contains(nums, 10) // false

// 检查是否满足条件
slices.ContainsFunc(nums, func(n int) bool {
return n > 5
}) // true

// 查找元素索引
slices.Index(nums, 5) // 4
slices.Index(nums, 10) // -1(不存在)

// 使用自定义比较函数查找
type Person struct {
Name string
Age int
}
people := []Person{{"张三", 25}, {"李四", 30}}
idx := slices.IndexFunc(people, func(p Person) bool {
return p.Name == "李四"
}) // 1

// 二分查找(切片必须已排序)
sorted := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
idx, found := slices.BinarySearch(sorted, 5) // idx=4, found=true

排序

nums := []int{3, 1, 4, 1, 5, 9, 2, 6}

// 排序
slices.Sort(nums)
fmt.Println(nums) // [1 1 2 3 4 5 6 9]

// 检查是否已排序
slices.IsSorted(nums) // true

// 反向排序
slices.Reverse(nums)
fmt.Println(nums) // [9 6 5 4 3 2 1 1]

// 自定义排序
people := []Person{
{"张三", 25},
{"李四", 30},
{"王五", 20},
}
slices.SortFunc(people, func(a, b Person) int {
if a.Age < b.Age {
return -1
} else if a.Age > b.Age {
return 1
}
return 0
})
// 按年龄排序:王五(20), 张三(25), 李四(30)

// 稳定排序(相等元素保持原有顺序)
slices.SortStableFunc(people, func(a, b Person) int {
return cmp.Compare(a.Age, b.Age)
})

最值

nums := []int{3, 1, 4, 1, 5, 9, 2, 6}

minVal := slices.Min(nums) // 1
maxVal := slices.Max(nums) // 9

// 自定义比较
youngest := slices.MinFunc(people, func(a, b Person) int {
return cmp.Compare(a.Age, b.Age)
})

插入和删除

nums := []int{1, 2, 3, 4, 5}

// 在索引 2 处插入元素
nums = slices.Insert(nums, 2, 10, 20)
fmt.Println(nums) // [1 2 10 20 3 4 5]

// 删除索引 2-4 的元素
nums = slices.Delete(nums, 2, 4)
fmt.Println(nums) // [1 2 3 4 5]

// 按条件删除
nums = slices.DeleteFunc(nums, func(n int) bool {
return n%2 == 0 // 删除偶数
})
fmt.Println(nums) // [1 3 5]

复制和比较

// 克隆切片
original := []int{1, 2, 3}
clone := slices.Clone(original)

// 比较
slices.Equal([]int{1, 2, 3}, []int{1, 2, 3}) // true
slices.Equal([]int{1, 2, 3}, []int{1, 2}) // false

// 自定义比较
slices.EqualFunc([]string{"a", "b"}, []string{"A", "B"}, strings.EqualFold) // true

// 比较大小
slices.Compare([]int{1, 2, 3}, []int{1, 2, 4}) // -1
slices.Compare([]int{1, 2, 3}, []int{1, 2, 2}) // 1
slices.Compare([]int{1, 2, 3}, []int{1, 2, 3}) // 0

其他实用函数

nums := []int{1, 1, 2, 2, 3, 3}

// 去重(相邻重复元素)
unique := slices.Compact(slices.Clone(nums))
fmt.Println(unique) // [1 2 3]

// 扩容(预分配空间)
nums = slices.Grow(nums, 10) // 确保有足够容量

// 裁剪(移除多余容量)
nums = slices.Clip(nums)

// 拼接多个切片
combined := slices.Concat([]int{1, 2}, []int{3, 4}, []int{5})
fmt.Println(combined) // [1 2 3 4 5]

// 替换元素
nums = slices.Replace([]int{1, 2, 3, 4, 5}, 1, 3, 10, 20)
fmt.Println(nums) // [1 10 20 4 5]

// Repeat:重复切片(Go 1.23+)
repeated := slices.Repeat([]int{1, 2}, 3)
fmt.Println(repeated) // [1 2 1 2 1 2]

迭代器函数(Go 1.23+)

Go 1.23 为 slices 包添加了与迭代器相关的函数,让切片遍历更加灵活:

nums := []int{10, 20, 30, 40, 50}

// Values:返回元素迭代器
for v := range slices.Values(nums) {
fmt.Println(v) // 10, 20, 30, 40, 50
}

// All:返回索引和值的迭代器
for i, v := range slices.All(nums) {
fmt.Printf("索引 %d: %d\n", i, v)
}

// Backward:反向遍历
for i, v := range slices.Backward(nums) {
fmt.Printf("反向 - 索引 %d: %d\n", i, v)
// 输出顺序: 4:50, 3:40, 2:30, 1:20, 0:10
}

// Collect:从迭代器收集元素到新切片
collected := slices.Collect(slices.Values(nums))
fmt.Println(collected) // [10 20 30 40 50]

// AppendSeq:将迭代器的元素追加到现有切片
base := []int{0}
result := slices.AppendSeq(base, slices.Values(nums))
fmt.Println(result) // [0 10 20 30 40 50]

// Sorted:从迭代器收集并排序
unsorted := []int{3, 1, 4, 1, 5, 9}
sorted := slices.Sorted(slices.Values(unsorted))
fmt.Println(sorted) // [1 1 3 4 5 9]

// SortedFunc:使用自定义比较函数排序
type Person struct {
Name string
Age int
}
people := []Person{{"张三", 25}, {"李四", 20}, {"王五", 30}}
sortedPeople := slices.SortedFunc(slices.Values(people), func(a, b Person) int {
if a.Age < b.Age {
return -1
} else if a.Age > b.Age {
return 1
}
return 0
})
// 按年龄排序:[{李四 20} {张三 25} {王五 30}]

// SortedStableFunc:稳定排序(相等元素保持原顺序)
stable := slices.SortedStableFunc(slices.Values(people), func(a, b Person) int {
return cmp.Compare(a.Age, b.Age)
})

// Chunk:将切片分块(Go 1.23+)
for chunk := range slices.Chunk(nums, 2) {
fmt.Println(chunk) // [10 20], [30 40], [50]
}

迭代器函数的实际应用

// 组合使用迭代器函数
func processNumbers(nums []int) []int {
// 过滤出偶数,然后平方
return slices.Collect(filterMap(
slices.Values(nums),
func(n int) (int, bool) {
if n%2 == 0 {
return n * n, true
}
return 0, false
},
))
}

// 通用的过滤映射迭代器
func filterMap[T, U any](seq iter.Seq[T], f func(T) (U, bool)) iter.Seq[U] {
return func(yield func(U) bool) {
for v := range seq {
if mapped, ok := f(v); ok {
if !yield(mapped) {
return
}
}
}
}
}

映射操作:maps 包

Go 1.21 引入了 maps 包,提供了映射操作的泛型函数。

基本操作

import "maps"

m := map[string]int{"a": 1, "b": 2, "c": 3}

// 克隆
clone := maps.Clone(m)

// 复制
dst := map[string]int{"a": 10}
maps.Copy(dst, m)
fmt.Println(dst) // map[a:1 b:2 c:3](原值被覆盖)

// 删除满足条件的元素
maps.DeleteFunc(m, func(k string, v int) bool {
return v%2 == 0 // 删除值为偶数的元素
})
fmt.Println(m) // map[a:1 c:3]

比较

m1 := map[string]int{"a": 1, "b": 2}
m2 := map[string]int{"a": 1, "b": 2}
m3 := map[string]int{"a": 1, "b": 3}

maps.Equal(m1, m2) // true
maps.Equal(m1, m3) // false

// 自定义比较
type Data struct{ Value int }
d1 := map[string]Data{"a": {1}}
d2 := map[string]Data{"a": {1}}
maps.EqualFunc(d1, d2, func(v1, v2 Data) bool {
return v1.Value == v2.Value
}) // true

迭代器(Go 1.23+)

m := map[string]int{"a": 1, "b": 2, "c": 3}

// 获取所有键
keys := slices.Collect(maps.Keys(m))
// 或者排序后获取
sortedKeys := slices.Sorted(maps.Keys(m))

// 获取所有值
values := slices.Collect(maps.Values(m))

// 遍历
for k, v := range maps.All(m) {
fmt.Printf("%s: %d\n", k, v)
}

比较工具:cmp 包

Go 1.21 引入了 cmp 包,提供了有序类型比较的工具函数。

基本比较

import "cmp"

// Compare 返回 -1、0、1
cmp.Compare(1, 2) // -1
cmp.Compare(2, 2) // 0
cmp.Compare(3, 2) // 1

// Less 判断是否小于
cmp.Less(1, 2) // true
cmp.Less(2, 1) // false

// 支持所有有序类型
cmp.Compare("apple", "banana") // -1(字符串按字典序)
cmp.Compare(3.14, 2.71) // 1

比较 complex 类型(Go 1.21+)

// Compare 支持比较复数
c1 := complex(1, 2)
c2 := complex(1, 3)
cmp.Compare(c1, c2) // -1

值唯一化:unique 包(Go 1.23+)

Go 1.23 引入了 unique 包,用于值的规范化(canonicalization)。当你需要确保相同值的实例只有一个时,这个包非常有用,可以减少内存使用并允许 O(1) 的值比较。

基本概念

unique 包的核心是 Handle[T] 类型,它代表一个规范化的值:

import "unique"

// 创建规范化句柄
h1 := unique.Make("hello")
h2 := unique.Make("hello")
h3 := unique.Make("world")

// 相同值的句柄是相同的
fmt.Println(h1 == h2) // true
fmt.Println(h1 == h3) // false

// 获取原始值
fmt.Println(h1.Value()) // "hello"

工作原理

当使用 unique.Make 创建句柄时,如果该值已经存在,则返回现有的句柄。这意味着内存中相同值只有一个实例:

// 内存中只有一个 "hello" 字符串实例
h1 := unique.Make("hello")
h2 := unique.Make("hello")

// h1 和 h2 指向同一个底层字符串
// 比较句柄是 O(1) 操作
if h1 == h2 {
fmt.Println("相同的值")
}

实际应用:字符串去重

当程序需要处理大量重复字符串时,使用 unique 可以显著减少内存使用:

package main

import (
"fmt"
"unique"
)

type Document struct {
ID int
Author unique.Handle[string] // 使用句柄存储作者名
Category unique.Handle[string] // 使用句柄存储分类
Title string
}

func main() {
docs := []Document{
{ID: 1, Author: unique.Make("张三"), Category: unique.Make("技术"), Title: "Go 入门"},
{ID: 2, Author: unique.Make("张三"), Category: unique.Make("技术"), Title: "Go 进阶"},
{ID: 3, Author: unique.Make("李四"), Category: unique.Make("生活"), Title: "日常随笔"},
}

// 查找所有张三的文章
zhangSan := unique.Make("张三")
for _, doc := range docs {
if doc.Author == zhangSan {
fmt.Printf("找到文章: %s\n", doc.Title)
}
}
}

实际应用:符号表

在编译器、解释器等场景中,unique 可以用于实现高效的符号表:

package main

import (
"fmt"
"unique"
)

// 符号类型
type Symbol = unique.Handle[string]

// 符号表
type SymbolTable struct {
symbols map[Symbol]int
}

func NewSymbolTable() *SymbolTable {
return &SymbolTable{
symbols: make(map[Symbol]int),
}
}

func (st *SymbolTable) Define(name string, value int) {
sym := unique.Make(name)
st.symbols[sym] = value
}

func (st *SymbolTable) Lookup(name string) (int, bool) {
sym := unique.Make(name)
val, ok := st.symbols[sym]
return val, ok
}

func main() {
st := NewSymbolTable()

st.Define("x", 10)
st.Define("y", 20)
st.Define("x", 30) // 覆盖之前的值

if val, ok := st.Lookup("x"); ok {
fmt.Printf("x = %d\n", val) // x = 30
}
}

与 sync.Pool 的区别

特性unique.Handlesync.Pool
目的值的规范化对象复用
相等比较O(1)需要比较值
内存管理自动 GC可能被回收
适用场景相同值多次使用减少对象分配

注意事项

  1. 值必须是可比较的:只有可比较的类型才能使用 unique.Make
  2. 句柄的生命周期:句柄会保持对值的引用,直到句柄不再被使用
  3. 不适用于频繁变化的值:每次值变化都会创建新的句柄
// ✅ 适合:相同的值多次出现
for _, name := range []string{"a", "b", "a", "c", "a"} {
h := unique.Make(name) // "a" 只会在内存中存储一次
}

// ❌ 不适合:值频繁变化
for i := 0; i < 1000; i++ {
h := unique.Make(fmt.Sprintf("item-%d", i)) // 每个都是不同的值
}

Ordered 约束

cmp.Ordered 是一个类型约束,表示支持比较运算符的类型:

// Ordered 包含所有有序类型
type Ordered interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~string
}

// 用于泛型函数
func Max[T cmp.Ordered](a, b T) T {
if cmp.Compare(a, b) >= 0 {
return a
}
return b
}

Or 函数(Go 1.22+)

cmp.Or 返回第一个非零值:

// 从多个值中选择第一个非零值
name := ""
defaultName := "匿名"
result := cmp.Or(name, defaultName) // "匿名"

// 链式调用
config := cmp.Or(userConfig, defaultConfig, fallbackConfig)

// 在排序中使用
type Order struct {
Customer string
Product string
Price float64
}

orders := []Order{{"bob", "foo", 1}, {"alice", "foo", 2}}
slices.SortFunc(orders, func(a, b Order) int {
// 先按客户排序,再按产品排序,最后按价格降序
return cmp.Or(
cmp.Compare(a.Customer, b.Customer),
cmp.Compare(a.Product, b.Product),
cmp.Compare(b.Price, a.Price), // 注意顺序,降序
)
})

NaN 处理

cmp.Compare 对 NaN 有特殊处理:

// 标准比较中,NaN 不等于任何值(包括自身)
nan := math.NaN()
nan == nan // false

// cmp.Compare 对 NaN 有明确定义
cmp.Compare(nan, 1.0) // -1(NaN 被认为小于任何非 NaN 值)
cmp.Compare(nan, nan) // 0(NaN 等于 NaN)

值规范化:unique 包(Go 1.23+)

Go 1.23 引入了 unique 包,用于值的规范化(也称为"字符串驻留"或"哈希共享")。当你有大量重复的值时,使用 unique 包可以显著减少内存使用。

基本概念

unique 包的核心思想是:对于相同的值,只存储一份副本,所有引用都指向这个规范化的副本。这在处理大量重复字符串时特别有用。

import "unique"

// Make 创建一个值的规范化句柄
handle := unique.Make("hello")

// 通过 Value 方法获取规范化后的值
fmt.Println(handle.Value()) // "hello"

实际应用:字符串去重

package main

import (
"fmt"
"unique"
)

func main() {
// 假设从数据库或网络读取了大量的用户数据
// 很多用户可能来自同一个城市
users := []struct {
Name string
City string
}{
{"张三", "北京"},
{"李四", "北京"},
{"王五", "上海"},
{"赵六", "北京"},
{"钱七", "上海"},
}

// 使用 unique.Make 规范化城市名称
// 相同的城市名会指向同一个内存地址
cityHandles := make([]unique.Handle[string], len(users))
for i, user := range users {
cityHandles[i] = unique.Make(user.City)
}

// 检查两个用户是否来自同一个城市
// Handle 的比较非常高效,只是指针比较
if cityHandles[0] == cityHandles[1] {
fmt.Println("张三和李四来自同一个城市")
}

// 获取实际的值
fmt.Println("张三的城市:", cityHandles[0].Value())
}

工作原理

unique.Handle[T] 是一个不可变的句柄类型:

// 两个 Handle 相等当且仅当它们指向同一个规范化值
h1 := unique.Make("hello")
h2 := unique.Make("hello")
h3 := unique.Make("world")

fmt.Println(h1 == h2) // true(指向同一个规范化值)
fmt.Println(h1 == h3) // false

// 获取规范化的值
value := h1.Value() // "hello"

内存优势:当程序中有大量重复的字符串(如配置项、标签、城市名等),使用 unique 包可以将内存使用量减少到原来的几分之一甚至更少。

适用场景

// 场景一:大量重复的字符串值
type LogEntry struct {
Level unique.Handle[string] // 日志级别:DEBUG, INFO, WARN, ERROR
Service unique.Handle[string] // 服务名称
Message string
}

// 场景二:大型结构的去重
type Config struct {
Settings unique.Handle[map[string]string]
}

// 场景三:作为 map 的键,提高比较效率
type CacheKey struct {
Namespace unique.Handle[string]
Key string
}

结构体布局:structs 包(Go 1.23+)

Go 1.23 引入了 structs 包,提供修改结构体属性的类型。目前主要包含 HostLayout 类型。

HostLayout 类型

HostLayout 用于标记结构体具有符合主机平台预期的内存布局。当与 C 代码交互或使用系统 API 时,这非常重要。

import "structs"

// 用于与 C 代码交互的结构体
type CStruct struct {
// HostLayout 确保结构体布局符合主机平台预期
_ structs.HostLayout

Field1 int32
Field2 int64
Field3 float64
}

用途

  1. 与 C 代码交互:确保 Go 结构体与 C 结构体的内存布局一致
  2. 系统调用:传递给操作系统 API 的结构体需要正确的对齐
  3. 网络协议:某些协议要求特定的内存布局
package main

import (
"fmt"
"structs"
"unsafe"
)

// 用于系统调用的结构体
type SyscallBuffer struct {
_ structs.HostLayout // 标记为主机布局
Len uintptr
Cap uintptr
Data *byte
}

func main() {
var buf SyscallBuffer

// 可以安全地传递给 C 函数或系统调用
fmt.Printf("Size: %d\n", unsafe.Sizeof(buf))
}

注意HostLayout 字段本身不占用内存(零大小),它只是一个编译器标记。

为什么需要 HostLayout?

Go 语言规范不保证结构体字段的内存顺序。虽然当前 Go 编译器的字段顺序与声明顺序一致,但这不是语言保证。HostLayout 明确告诉编译器:"这个结构体需要与主机平台的预期一致"。

// 没有 HostLayout:字段顺序可能被重排
type RegularStruct struct {
A uint8
B uint64
C uint8
}

// 有 HostLayout:字段顺序保证与主机平台一致
type HostStruct struct {
_ structs.HostLayout
A uint8
B uint64
C uint8
}

随机数生成:math/rand/v2 包(Go 1.22+)

Go 1.22 引入了 math/rand/v2 包,这是标准库中第一个"v2"版本的包。相比 math/rand,它提供了更好的 API 和更现代的随机数算法。

主要改进

import "math/rand/v2"

// 1. 全局随机数生成器自动随机种子
// 不再需要手动调用 rand.Seed()
n := rand.Intn(100) // 每次运行产生不同的结果

// 2. 新的 N 函数:类型安全的范围随机数
// 旧方式(math/rand)
// n := rand.Intn(100)

// 新方式(math/rand/v2)- 泛型支持
n = rand.N(100) // 0 到 99 的随机 int
f := rand.N(3.14) // 0 到 3.14 的随机 float64
d := rand.N(5 * time.Minute) // 0 到 5分钟的随机 Duration

// 3. 更好的 API 命名
i32 := rand.Int32() // 返回 int32
i64 := rand.Int64() // 返回 int64
u32 := rand.Uint32() // 返回 uint32
u64 := rand.Uint64() // 返回 uint64

// 带范围的版本
i32n := rand.Int32N(100) // 0 到 99
i64n := rand.Int64N(1000)
u32n := rand.Uint32N(100)
u64n := rand.Uint64N(1000)

// 泛型的 UintN
un := rand.UintN[uint64](100)

新的随机数算法

math/rand/v2 提供了两种现代的伪随机数生成器:

import "math/rand/v2"

// ChaCha8:加密强度高的随机数生成器(默认使用)
// 适合需要安全性的场景
var chaCha8 rand.ChaCha8
chaCha8.Seed([32]byte{1, 2, 3, /* ... */})
r1 := rand.New(&chaCha8)

// PCG:高性能的伪随机数生成器
// 适合需要高性能但不需要加密强度的场景
var pcg rand.PCG
pcg.Seed(uint64(12345))
r2 := rand.New(&pcg)

// 使用自定义源生成随机数
fmt.Println(r1.IntN(100))
fmt.Println(r2.IntN(100))

兼容性说明

// math/rand 仍然可用,保持向后兼容
import "math/rand"

// math/rand/v2 是新包,API 有变化
import "math/rand/v2"

// 主要区别:
// 1. v2 的全局生成器自动随机种子
// 2. v2 移除了 Read 方法(应使用 crypto/rand)
// 3. v2 使用新的命名约定(IntN 而非 Intn)
// 4. v2 使用更快的算法

使用建议

import (
"crypto/rand"
"math/rand/v2"
)

func main() {
// 需要安全性(如生成令牌、密码):使用 crypto/rand
token := make([]byte, 32)
rand.Read(token)

// 不需要安全性(如模拟、游戏、测试):使用 math/rand/v2
diceRoll := rand.N(6) + 1 // 1-6

// 需要可重复的随机序列:创建自定义源
var src rand.PCG
src.Seed(42)
r := rand.New(&src)
// 使用 r 生成随机数,每次运行 Seed(42) 会产生相同的序列
}

弱指针:weak 包(Go 1.24+)

Go 1.24 引入了 weak 包,提供弱指针(Weak Pointer)功能。弱指针是一种不会阻止垃圾回收器回收其引用对象的指针,非常适合构建内存高效的数据结构。

基本概念

普通指针(强指针)会阻止垃圾回收器回收其指向的对象。而弱指针不会阻止对象被回收——当对象只被弱指针引用时,它仍然可以被垃圾回收。

import "runtime/weak"

type Widget struct {
data []byte
}

func main() {
// 创建一个对象
widget := &Widget{data: make([]byte, 1024)}

// 创建弱指针
weakPtr := weak.Make(widget)

// 通过弱指针获取对象
if ptr := weakPtr.Value(); ptr != nil {
fmt.Println("对象仍然存在")
}

// 当原对象被垃圾回收后,弱指针返回 nil
widget = nil
runtime.GC()

if ptr := weakPtr.Value(); ptr == nil {
fmt.Println("对象已被回收")
}
}

实际应用:弱引用 Map

弱指针最常见的用途是实现弱引用 Map,其中键或值是弱引用的:

package main

import (
"fmt"
"runtime"
"runtime/weak"
"sync"
)

// WeakMap 是一个弱引用值的 Map
type WeakMap[K comparable, V any] struct {
mu sync.RWMutex
store map[K]weak.Pointer[V]
}

func NewWeakMap[K comparable, V any]() *WeakMap[K, V] {
return &WeakMap[K, V]{
store: make(map[K]weak.Pointer[V]),
}
}

// Set 设置键值对,值为弱引用
func (m *WeakMap[K, V]) Set(key K, value *V) {
m.mu.Lock()
defer m.mu.Unlock()
m.store[key] = weak.Make(value)
}

// Get 获取值,如果值已被回收则返回 nil
func (m *WeakMap[K, V]) Get(key K) *V {
m.mu.RLock()
defer m.mu.RUnlock()

if weakPtr, ok := m.store[key]; ok {
return weakPtr.Value()
}
return nil
}

// Delete 删除键
func (m *WeakMap[K, V]) Delete(key K) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.store, key)
}

// Clean 清理已被回收的条目
func (m *WeakMap[K, V]) Clean() {
m.mu.Lock()
defer m.mu.Unlock()

for key, weakPtr := range m.store {
if weakPtr.Value() == nil {
delete(m.store, key)
}
}
}

func main() {
cache := NewWeakMap[string, []byte]()

// 存储数据
data := make([]byte, 1024)
cache.Set("key1", &data)

// 获取数据
if val := cache.Get("key1"); val != nil {
fmt.Println("找到数据")
}

// 清除强引用,允许垃圾回收
data = nil
runtime.GC()

// 数据可能已被回收
if val := cache.Get("key1"); val == nil {
fmt.Println("数据已被回收")
}
}

使用场景

  1. 缓存系统:缓存可以被垃圾回收,不会导致内存泄漏
  2. 规范映射:类似于 unique 包,但更灵活
  3. 观察者模式:观察者可以被自动清理
  4. 对象池:对象可被回收时不阻止 GC
// 示例:带弱引用的事件监听器
type EventEmitter struct {
listeners []weak.Pointer[func(string)]
mu sync.RWMutex
}

func (e *EventEmitter) Subscribe(handler *func(string)) {
e.mu.Lock()
defer e.mu.Unlock()
e.listeners = append(e.listeners, weak.Make(handler))
}

func (e *EventEmitter) Emit(message string) {
e.mu.RLock()
defer e.mu.RUnlock()

for _, weakHandler := range e.listeners {
if handler := weakHandler.Value(); handler != nil {
(*handler)(message)
}
}
}

与 unique 包的区别

特性weak.Pointerunique.Handle
目的弱引用对象值规范化
对象生命周期可被 GC 回收保持引用
比较效率O(1) 指针比较O(1) 指针比较
适用场景缓存、观察者符号表、字符串去重

改进的清理机制:runtime.AddCleanup(Go 1.24+)

Go 1.24 引入了 runtime.AddCleanup,作为 runtime.SetFinalizer 的替代方案。它更灵活、更高效,避免了 SetFinalizer 的许多问题。

基本用法

import "runtime"

type Resource struct {
file *os.File
}

func NewResource(path string) (*Resource, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}

res := &Resource{file: file}

// 注册清理函数
runtime.AddCleanup(res, func(f *os.File) {
fmt.Println("清理资源...")
f.Close()
}, file)

return res, nil
}

func main() {
res, _ := NewResource("test.txt")
// 使用 res...

// 当 res 不再被引用时,清理函数会被调用
res = nil
runtime.GC() // 触发垃圾回收
}

AddCleanup vs SetFinalizer

import "runtime"

type MyStruct struct {
data []byte
}

// 使用 SetFinalizer(旧方式)
func oldWay() {
obj := &MyStruct{data: make([]byte, 100)}
runtime.SetFinalizer(obj, func(o *MyStruct) {
fmt.Println("清理对象")
})
}

// 使用 AddCleanup(新方式,推荐)
func newWay() {
obj := &MyStruct{data: make([]byte, 100)}
runtime.AddCleanup(obj, func(data []byte) {
fmt.Println("清理对象")
}, obj.data)
}

AddCleanup 的优势

  1. 多个清理函数:可以为同一个对象注册多个清理函数
  2. 内部指针:可以附加到对象的内部字段
  3. 避免循环引用:不会阻止对象被回收
  4. 不延迟回收:对象可以被立即回收
type Connection struct {
conn net.Conn
buf []byte
}

func NewConnection(addr string) (*Connection, error) {
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}

c := &Connection{
conn: conn,
buf: make([]byte, 4096),
}

// 可以注册多个清理函数
runtime.AddCleanup(c, func(conn net.Conn) {
conn.Close()
}, c.conn)

runtime.AddCleanup(c, func(buf []byte) {
fmt.Println("释放缓冲区")
}, c.buf)

return c, nil
}

清理函数的取消

func main() {
obj := &MyStruct{}

// AddCleanup 返回一个取消函数
cancel := runtime.AddCleanup(obj, func() {
fmt.Println("清理")
}, nil)

// 可以手动取消清理
cancel()

// 或者让清理在对象被回收时自动执行
}

最佳实践

// 推荐:使用 AddCleanup 管理资源
type FileWrapper struct {
file *os.File
}

func OpenFile(path string) (*FileWrapper, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}

fw := &FileWrapper{file: f}

// 注册清理函数
runtime.AddCleanup(fw, func(file *os.File) {
file.Close()
}, f)

return fw, nil
}

// 更推荐的方式:仍然使用 defer
func ProcessFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // 这是更可控的方式

// 处理文件...
return nil
}

字符串迭代器:strings 包新函数(Go 1.24+)

Go 1.24 为 strings 包添加了几个返回迭代器的函数:

import "strings"

func main() {
s := "Hello\nWorld\nGo"

// Lines:按行迭代
for line := range strings.Lines(s) {
fmt.Println("行:", line)
}

// SplitSeq:按分隔符分割的迭代器
for part := range strings.SplitSeq("a,b,c", ",") {
fmt.Println(part)
}

// SplitAfterSeq:分割后保留分隔符
for part := range strings.SplitAfterSeq("a,b,c", ",") {
fmt.Println(part) // "a,", "b,", "c"
}

// FieldsSeq:按空白字符分割
for word := range strings.FieldsSeq("hello world go") {
fmt.Println(word)
}

// FieldsFuncSeq:自定义分割条件
for word := range strings.FieldsFuncSeq("a1b2c3", func(r rune) bool {
return r >= '0' && r <= '9'
}) {
fmt.Println(word) // "a", "b", "c"
}
}

字节切片迭代器:bytes 包新函数(Go 1.24+)

Go 1.24 为 bytes 包添加了类似的迭代器函数:

import "bytes"

func main() {
data := []byte("Hello\nWorld\nGo")

// Lines:按行迭代
for line := range bytes.Lines(data) {
fmt.Println("行:", string(line))
}

// SplitSeq:按分隔符分割
for part := range bytes.SplitSeq([]byte("a,b,c"), []byte(",")) {
fmt.Println(string(part))
}

// SplitAfterSeq:分割后保留分隔符
for part := range bytes.SplitAfterSeq([]byte("a,b,c"), []byte(",")) {
fmt.Println(string(part))
}

// FieldsSeq:按空白字符分割
for word := range bytes.FieldsSeq([]byte("hello world go")) {
fmt.Println(string(word))
}

// FieldsFuncSeq:自定义分割条件
for word := range bytes.FieldsFuncSeq([]byte("a1b2c3"), func(r rune) bool {
return r >= '0' && r <= '9'
}) {
fmt.Println(string(word))
}
}

加密扩展包(Go 1.24+)

Go 1.24 添加了几个新的加密相关包:

crypto/hkdf

import (
"crypto/hkdf"
"crypto/sha256"
)

func main() {
// 从密钥材料派生密钥
secret := []byte("secret-key-material")
salt := []byte("salt")
info := []byte("context-info")

// 创建 HKDF 实例
hkdf := hkdf.New(sha256.New, secret, salt, info)

// 派生 32 字节的密钥
key := make([]byte, 32)
hkdf.Read(key)
fmt.Printf("派生密钥: %x\n", key)
}

crypto/pbkdf2

import (
"crypto/pbkdf2"
"crypto/sha256"
)

func main() {
// 从密码派生密钥
password := []byte("user-password")
salt := []byte("random-salt")

// 使用 PBKDF2 派生密钥
key := pbkdf2.Key(password, salt, 10000, 32, sha256.New)
fmt.Printf("派生密钥: %x\n", key)
}

crypto/sha3

import "crypto/sha3"

func main() {
data := []byte("Hello, World!")

// SHA3-256
hash := sha3.Sum256(data)
fmt.Printf("SHA3-256: %x\n", hash)

// SHA3-512
hash512 := sha3.Sum512(data)
fmt.Printf("SHA3-512: %x\n", hash512)

// SHAKE128(可变长度输出)
shake := sha3.NewShake128()
shake.Write(data)
out := make([]byte, 64)
shake.Read(out)
fmt.Printf("SHAKE128: %x\n", out)
}

常用包快速参考

基础包

包名说明典型用法
fmt格式化 I/Ofmt.Println, fmt.Sprintf
os操作系统接口文件操作、环境变量
io基本 I/O 接口io.Copy, io.ReadAll
bufio缓冲 I/Obufio.Scanner, bufio.Writer
strings字符串操作strings.Split, strings.Contains
strconv字符串转换strconv.Atoi, strconv.Itoa
bytes字节切片操作bytes.Buffer
time时间处理time.Now, time.Parse
math数学函数math.Sqrt, math.Abs
log/slog结构化日志(Go 1.21+)slog.Info, slog.With

集合操作包(Go 1.21+)

包名说明典型用法
slices切片泛型操作slices.Sort, slices.Contains, slices.Values
maps映射泛型操作maps.Clone, maps.Copy, maps.Keys, maps.Values
cmp比较工具cmp.Compare, cmp.Less, cmp.Or

迭代器与新特性包(Go 1.22+/1.23+)

包名说明典型用法
iter迭代器定义(Go 1.23+)iter.Seq, iter.Seq2
unique值规范化(Go 1.23+)unique.Make, unique.Handle
structs结构体布局(Go 1.23+)structs.HostLayout
math/rand/v2随机数生成(Go 1.22+)rand.N, rand.Int64N, rand.ChaCha8

编码包

包名说明典型用法
encoding/jsonJSON 编解码json.Marshal, json.Unmarshal
encoding/xmlXML 编解码xml.Marshal, xml.Unmarshal
encoding/csvCSV 处理csv.Reader, csv.Writer
encoding/base64Base64 编解码base64.StdEncoding
encoding/hex十六进制编解码hex.EncodeToString
encoding/binary二进制数据字节序转换

网络包

包名说明典型用法
net网络编程基础net.Dial, net.Listen
net/httpHTTP 客户端和服务端http.Get, http.ListenAndServe
net/urlURL 解析url.Parse, url.QueryEscape
net/mail邮件地址解析mail.ParseAddress

加密包

包名说明典型用法
crypto/md5MD5 哈希md5.New
crypto/sha256SHA256 哈希sha256.Sum256
crypto/aesAES 加密对称加密
crypto/rand加密安全随机数rand.Read
crypto/tlsTLS 协议HTTPS

并发包

包名说明典型用法
sync同步原语sync.Mutex, sync.WaitGroup
sync/atomic原子操作atomic.AddInt64
context上下文管理取消信号、超时控制

查看标准库文档

在线文档

访问 Go 官方包文档网站:https://pkg.go.dev/std

本地文档

# 启动本地文档服务器
go doc -http=:6060

# 然后在浏览器访问 http://localhost:6060

命令行查看

# 查看包文档
go doc fmt

# 查看函数文档
go doc fmt.Println

# 查看完整文档
go doc -all fmt

# 查看包中的所有符号
go doc -short fmt

使用建议

优先使用标准库

在引入第三方依赖之前,先检查标准库是否已经提供了需要的功能。标准库的优势包括:

  • 经过充分测试和审查
  • 长期维护,API 稳定
  • 无额外依赖,减少供应链风险
  • 性能经过优化

注意废弃的 API

部分 API 会随着版本更新而废弃:

// 已废弃:io/ioutil(Go 1.16 起)
import "io/ioutil"
ioutil.ReadFile // -> os.ReadFile
ioutil.WriteFile // -> os.WriteFile
ioutil.ReadDir // -> os.ReadDir
ioutil.TempDir // -> os.MkdirTemp
ioutil.TempFile // -> os.CreateTemp

理解接口组合

掌握标准库的核心接口,能让你更好地组合使用各种功能:

// 一个函数接受 io.Reader,可以处理文件、网络连接、字符串等
func processData(r io.Reader) error {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
// 处理每一行
}
return scanner.Err()
}

// 调用时可以传入不同的 Reader
processData(file) // 文件
processData(strings.NewReader("...")) // 字符串
processData(resp.Body) // HTTP 响应
processData(gzReader) // gzip 压缩数据

注意并发安全

标准库的大多数类型不是并发安全的,除非文档明确说明:

// 不安全:多个 goroutine 同时操作
var m = make(map[string]int)
go func() { m["a"] = 1 }()
go func() { _ = m["a"] }()

// 安全:使用 sync.Map
var m sync.Map
m.Store("a", 1)
v, ok := m.Load("a")

// 安全:使用互斥锁保护
var (
mu sync.Mutex
m = make(map[string]int)
)
mu.Lock()
m["a"] = 1
mu.Unlock()

小结

Go 标准库的设计遵循"小接口、大组合"的理念,核心接口简单但功能强大。掌握标准库的关键在于:

  1. 理解核心接口(io.Readerio.Writer 等)
  2. 学会组合使用不同的包
  3. 了解常用函数的用法
  4. 知道去哪里查看文档

标准库是 Go 语言的重要组成部分,深入学习标准库不仅能提高开发效率,也能帮助你写出更符合 Go 风格的代码。

下一步

参考资源