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.EOF。Write 方法将 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.Reader、io.Writer 和 io.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.Builder 与 bytes.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 保留设备名(如
NUL、COM1)
何时使用 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 引入了三个新的内置函数:min、max 和 clear,这些函数让常见的操作变得更加简洁。
min 和 max 函数
min 和 max 函数用于计算多个值中的最小值和最大值:
// 基本用法:比较两个值
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)
}
类型约束:min 和 max 支持所有支持比较运算符的类型,包括:
- 整数类型:
int、int8、int16、int32、int64及其无符号版本 - 浮点类型:
float32、float64 - 字符串类型:
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
// 迭代完成
// 循环结束
提前终止迭代
当循环提前退出(如使用 break 或 return)时,迭代器会被通知:
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)
}
}
拉迭代器适用于需要手动控制迭代的场景,如:
- 在迭代过程中需要做复杂的状态管理
- 需要跨多个函数传递迭代状态
- 与其他语言的迭代器模式对接
迭代器的优势
- 统一的遍历接口:所有可迭代类型都可以使用相同的
for range语法 - 惰性求值:只在需要时计算下一个值,节省内存
- 可组合性:可以轻松组合多个迭代操作
- 资源安全:提前退出时会正确清理资源
// 组合迭代操作示例
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.Handle | sync.Pool |
|---|---|---|
| 目的 | 值的规范化 | 对象复用 |
| 相等比较 | O(1) | 需要比较值 |
| 内存管理 | 自动 GC | 可能被回收 |
| 适用场景 | 相同值多次使用 | 减少对象分配 |
注意事项
- 值必须是可比较的:只有可比较的类型才能使用
unique.Make - 句柄的生命周期:句柄会保持对值的引用,直到句柄不再被使用
- 不适用于频繁变化的值:每次值变化都会创建新的句柄
// ✅ 适合:相同的值多次出现
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
}
用途:
- 与 C 代码交互:确保 Go 结构体与 C 结构体的内存布局一致
- 系统调用:传递给操作系统 API 的结构体需要正确的对齐
- 网络协议:某些协议要求特定的内存布局
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("数据已被回收")
}
}
使用场景
- 缓存系统:缓存可以被垃圾回收,不会导致内存泄漏
- 规范映射:类似于
unique包,但更灵活 - 观察者模式:观察者可以被自动清理
- 对象池:对象可被回收时不阻止 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.Pointer | unique.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 的优势
- 多个清理函数:可以为同一个对象注册多个清理函数
- 内部指针:可以附加到对象的内部字段
- 避免循环引用:不会阻止对象被回收
- 不延迟回收:对象可以被立即回收
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/O | fmt.Println, fmt.Sprintf |
os | 操作系统接口 | 文件操作、环境变量 |
io | 基本 I/O 接口 | io.Copy, io.ReadAll |
bufio | 缓冲 I/O | bufio.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/json | JSON 编解码 | json.Marshal, json.Unmarshal |
encoding/xml | XML 编解码 | xml.Marshal, xml.Unmarshal |
encoding/csv | CSV 处理 | csv.Reader, csv.Writer |
encoding/base64 | Base64 编解码 | base64.StdEncoding |
encoding/hex | 十六进制编解码 | hex.EncodeToString |
encoding/binary | 二进制数据 | 字节序转换 |
网络包
| 包名 | 说明 | 典型用法 |
|---|---|---|
net | 网络编程基础 | net.Dial, net.Listen |
net/http | HTTP 客户端和服务端 | http.Get, http.ListenAndServe |
net/url | URL 解析 | url.Parse, url.QueryEscape |
net/mail | 邮件地址解析 | mail.ParseAddress |
加密包
| 包名 | 说明 | 典型用法 |
|---|---|---|
crypto/md5 | MD5 哈希 | md5.New |
crypto/sha256 | SHA256 哈希 | sha256.Sum256 |
crypto/aes | AES 加密 | 对称加密 |
crypto/rand | 加密安全随机数 | rand.Read |
crypto/tls | TLS 协议 | 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 标准库的设计遵循"小接口、大组合"的理念,核心接口简单但功能强大。掌握标准库的关键在于:
- 理解核心接口(
io.Reader、io.Writer等) - 学会组合使用不同的包
- 了解常用函数的用法
- 知道去哪里查看文档
标准库是 Go 语言的重要组成部分,深入学习标准库不仅能提高开发效率,也能帮助你写出更符合 Go 风格的代码。