编程基础 0008_标准库进阶

Go 标准库进阶

系统整理 Go 标准库中最常用的包,重点覆盖 io、os、bufio、strings、time、fmt 等

1. io 包核心接口

Go 的 I/O 设计围绕几个核心接口展开,几乎所有 I/O 操作都基于它们。

// 最基础的两个接口
type Reader interface {
    Read(p []byte) (n int, err error)
}
type Writer interface {
    Write(p []byte) (n int, err error)
}
type Closer interface {
    Close() error
}
// 组合接口
type ReadWriter interface {
    Reader
    Writer
}
type ReadCloser interface {
    Reader
    Closer
}
type WriteCloser interface {
    Writer
    Closer
}

谁实现了这些接口?

类型 Reader Writer Closer
*os.File Y Y Y
*bytes.Buffer Y Y -
*strings.Reader Y - -
net.Conn Y Y Y
*http.Request.Body Y - Y
*bufio.Reader Y - -
*bufio.Writer - Y -
*gzip.Reader Y - Y

2. io 常用函数

package main

import (
    "bytes"
    "fmt"
    "io"
    "os"
    "strings"
)

func main() {
    // io.Copy: 从 Reader 拷贝到 Writer(最常用)
    // 签名: func Copy(dst Writer, src Reader) (written int64, err error)
    r := strings.NewReader("Hello, io.Copy!")
    n, _ := io.Copy(os.Stdout, r) // 输出到标准输出
    fmt.Printf("\n拷贝了 %d 字节\n", n)

    // io.ReadAll: 读取全部内容(Go 1.16+, 替代 ioutil.ReadAll)
    r2 := strings.NewReader("Read all content")
    data, _ := io.ReadAll(r2)
    fmt.Println(string(data))

    // io.MultiReader: 将多个 Reader 串联成一个
    r3 := io.MultiReader(
        strings.NewReader("Part1 "),
        strings.NewReader("Part2 "),
        strings.NewReader("Part3"),
    )
    io.Copy(os.Stdout, r3) // Part1 Part2 Part3
    fmt.Println()

    // io.TeeReader: 读取时同时写入另一个 Writer(类似 tee 命令)
    var buf bytes.Buffer
    r4 := strings.NewReader("tee reader demo")
    tee := io.TeeReader(r4, &buf)
    io.ReadAll(tee)                     // 读取
    fmt.Println("buf:", buf.String())   // buf 中也有了数据

    // io.Pipe: 同步的内存管道(一端写另一端读)
    pr, pw := io.Pipe()
    go func() {
        fmt.Fprint(pw, "pipe data")
        pw.Close()
    }()
    pipeData, _ := io.ReadAll(pr)
    fmt.Println("pipe:", string(pipeData))

    // io.LimitReader: 限制读取字节数
    r5 := strings.NewReader("only read first 5 bytes")
    limited := io.LimitReader(r5, 5)
    data2, _ := io.ReadAll(limited)
    fmt.Println(string(data2)) // "only "
}

3. os 包

3.1 文件操作

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    // 创建文件
    f, err := os.Create("test.txt")
    if err != nil {
        panic(err)
    }
    f.WriteString("Hello, Go!\nSecond line.\n")
    f.Close()

    // 读取文件(Go 1.16+)
    data, err := os.ReadFile("test.txt")
    if err != nil {
        panic(err)
    }
    fmt.Println(string(data))

    // 写入文件(Go 1.16+)
    os.WriteFile("test2.txt", []byte("quick write"), 0644)

    // 打开文件(更多控制)
    f2, _ := os.OpenFile("test.txt", os.O_APPEND|os.O_WRONLY, 0644)
    f2.WriteString("Appended line.\n")
    f2.Close()

    // 文件信息
    info, _ := os.Stat("test.txt")
    fmt.Printf("文件名: %s, 大小: %d, 修改时间: %v\n",
        info.Name(), info.Size(), info.ModTime())

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

    // 复制文件
    src, _ := os.Open("test.txt")
    dst, _ := os.Create("test_copy.txt")
    io.Copy(dst, src)
    src.Close()
    dst.Close()

    // 清理
    os.Remove("test.txt")
    os.Remove("test2.txt")
    os.Remove("test_copy.txt")
}

3.2 目录操作

// 创建目录
os.Mkdir("mydir", 0755)
os.MkdirAll("a/b/c", 0755) // 递归创建

// 读取目录内容(Go 1.16+)
entries, _ := os.ReadDir(".")
for _, e := range entries {
    info, _ := e.Info()
    fmt.Printf("%-20s %10d %v\n", e.Name(), info.Size(), e.IsDir())
}

// 遍历目录树
filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
    if err != nil { return err }
    fmt.Println(path)
    return nil
})

// 临时文件和目录
tmpFile, _ := os.CreateTemp("", "prefix-*.txt")
fmt.Println(tmpFile.Name()) // /tmp/prefix-123456.txt
tmpFile.Close()
os.Remove(tmpFile.Name())

tmpDir, _ := os.MkdirTemp("", "myapp-")
fmt.Println(tmpDir) // /tmp/myapp-789012
os.RemoveAll(tmpDir)

3.3 环境变量

// 获取
home := os.Getenv("HOME")
path := os.Getenv("PATH")

// 设置(只影响当前进程)
os.Setenv("MY_VAR", "hello")

// 获取,带是否存在的判断
val, ok := os.LookupEnv("MY_VAR")
if ok {
    fmt.Println(val)
}

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

3.4 信号处理

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    // 监听信号
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

    fmt.Println("服务启动,按 Ctrl+C 退出...")

    // 阻塞等待信号
    sig := <-sigCh
    fmt.Printf("\n收到信号: %v,开始优雅关闭...\n", sig)

    // 清理资源...
    fmt.Println("关闭完成")
}

4. bufio 包

bufio 提供带缓冲的 I/O,减少系统调用次数。

4.1 Scanner 逐行读取

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    // 从字符串逐行读取
    input := "第一行\n第二行\n第三行"
    scanner := bufio.NewScanner(strings.NewReader(input))
    for scanner.Scan() {
        fmt.Println(">>", scanner.Text())
    }
    if err := scanner.Err(); err != nil {
        fmt.Println("错误:", err)
    }

    // 按单词分割
    wordScanner := bufio.NewScanner(strings.NewReader("hello world foo bar"))
    wordScanner.Split(bufio.ScanWords)
    for wordScanner.Scan() {
        fmt.Println("单词:", wordScanner.Text())
    }

    // 读取文件(最常用的模式)
    file, _ := os.Open("somefile.txt")
    defer file.Close()
    fileScanner := bufio.NewScanner(file)
    lineNum := 0
    for fileScanner.Scan() {
        lineNum++
        fmt.Printf("%d: %s\n", lineNum, fileScanner.Text())
    }

    // 处理超长行(默认 bufSize 64KB)
    longScanner := bufio.NewScanner(file)
    longScanner.Buffer(make([]byte, 1024*1024), 1024*1024) // 1MB buffer
}

4.2 Reader / Writer

// bufio.NewReader 带缓冲的读取
reader := bufio.NewReader(os.Stdin)
fmt.Print("请输入: ")
line, _ := reader.ReadString('\n') // 读到换行符为止
fmt.Println("你输入了:", line)

// Peek 预览但不消费
reader2 := bufio.NewReader(strings.NewReader("Hello World"))
peeked, _ := reader2.Peek(5)
fmt.Println(string(peeked)) // "Hello"(指针不移动)
full, _ := reader2.ReadString(' ')
fmt.Println(full) // "Hello "

// bufio.NewWriter 带缓冲的写入
file, _ := os.Create("output.txt")
writer := bufio.NewWriter(file)
writer.WriteString("缓冲写入第一行\n")
writer.WriteString("缓冲写入第二行\n")
writer.Flush() // 必须 Flush,否则数据可能还在缓冲区
file.Close()

5. strings 包

package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Hello, Go World!"

    // 查找
    fmt.Println(strings.Contains(s, "Go"))      // true
    fmt.Println(strings.HasPrefix(s, "Hello"))   // true
    fmt.Println(strings.HasSuffix(s, "World!"))  // true
    fmt.Println(strings.Index(s, "Go"))          // 7
    fmt.Println(strings.Count(s, "l"))           // 2

    // 分割与合并
    parts := strings.Split("a,b,c,d", ",")
    fmt.Println(parts)                                    // [a b c d]
    fmt.Println(strings.Join(parts, " | "))               // a | b | c | d
    fmt.Println(strings.Fields("  hello   world  foo "))  // [hello world foo]

    // 替换与转换
    fmt.Println(strings.Replace(s, "World", "Gopher", 1))
    fmt.Println(strings.ReplaceAll(s, "l", "L"))
    fmt.Println(strings.ToUpper(s))
    fmt.Println(strings.ToLower(s))
    fmt.Println(strings.Title("hello world"))  // Hello World (deprecated, use cases.Title)

    // 修剪
    fmt.Println(strings.TrimSpace("  hello  "))            // "hello"
    fmt.Println(strings.Trim("***hello***", "*"))          // "hello"
    fmt.Println(strings.TrimLeft("000123", "0"))           // "123"
    fmt.Println(strings.TrimPrefix("test_func", "test_"))  // "func"
    fmt.Println(strings.TrimSuffix("file.go", ".go"))      // "file"

    // 重复
    fmt.Println(strings.Repeat("ab", 3)) // "ababab"

    // Map: 对每个字符应用函数
    rot13 := strings.Map(func(r rune) rune {
        if r >= 'a' && r <= 'z' {
            return 'a' + (r-'a'+13)%26
        }
        return r
    }, "hello")
    fmt.Println(rot13) // "uryyb"
}

strings.Builder(高效字符串拼接)

// 低效:每次 + 都会分配新的字符串
func concatBad(n int) string {
    s := ""
    for i := 0; i < n; i++ {
        s += "a"
    }
    return s
}

// 高效:使用 Builder
func concatGood(n int) string {
    var b strings.Builder
    b.Grow(n) // 预分配,可选但推荐
    for i := 0; i < n; i++ {
        b.WriteString("a")
    }
    return b.String()
}

// 对比: n=10000 时
// concatBad:  ~50ms
// concatGood: ~0.01ms

strings.NewReader

// 把字符串变成 io.Reader
r := strings.NewReader("Hello, Reader!")
buf := make([]byte, 5)
for {
    n, err := r.Read(buf)
    if n > 0 {
        fmt.Print(string(buf[:n]))
    }
    if err != nil {
        break
    }
}

6. bytes 包

bytes 包和 strings 包 API 几乎一一对应,但操作的是 []byte 而非 string

// bytes.Buffer 是最常用的
var buf bytes.Buffer
buf.WriteString("Hello ")
buf.Write([]byte("World"))
buf.WriteByte('!')
fmt.Println(buf.String()) // "Hello World!"
fmt.Println(buf.Len())    // 12

// 也实现了 io.Reader 和 io.Writer
io.Copy(os.Stdout, &buf)

// bytes 包函数与 strings 包对应
fmt.Println(bytes.Contains([]byte("hello"), []byte("ell")))    // true
fmt.Println(bytes.Equal([]byte("abc"), []byte("abc")))          // true
parts := bytes.Split([]byte("a,b,c"), []byte(","))

何时用 bytes vs strings?
- 处理网络数据、文件内容 → bytes
- 处理文本字符串 → strings
- 需要反复修改内容 → bytes.Buffer
- 只需要拼接字符串 → strings.Builder

7. strconv 包

package main

import (
    "fmt"
    "strconv"
)

func main() {
    // int <-> string
    s := strconv.Itoa(42)              // "42"
    n, _ := strconv.Atoi("42")        // 42
    fmt.Println(s, n)

    // 指定进制
    s2 := strconv.FormatInt(255, 16)   // "ff"
    n2, _ := strconv.ParseInt("ff", 16, 64) // 255
    fmt.Println(s2, n2)

    // float <-> string
    s3 := strconv.FormatFloat(3.14159, 'f', 2, 64) // "3.14"
    f, _ := strconv.ParseFloat("3.14", 64)          // 3.14
    fmt.Println(s3, f)

    // bool <-> string
    s4 := strconv.FormatBool(true) // "true"
    b, _ := strconv.ParseBool("true") // true
    fmt.Println(s4, b)

    // Quote / Unquote
    fmt.Println(strconv.Quote(`hello "world"`))   // "hello \"world\""
    fmt.Println(strconv.Unquote(`"hello"`))        // hello
}

8. time 包

8.1 Go 独特的时间格式化

Go 不用 YYYY-MM-DD,而是用一个参考时间Mon Jan 2 15:04:05 MST 2006(即 2006年1月2日 下午3点4分5秒)。

记忆口诀:1月2日下午3点4分5秒2006年01/02 03:04:05PM '06

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()

    // 常用格式
    fmt.Println(now.Format("2006-01-02"))                // 2024-03-15
    fmt.Println(now.Format("2006-01-02 15:04:05"))       // 2024-03-15 14:30:00
    fmt.Println(now.Format("2006/01/02 03:04 PM"))       // 2024/03/15 02:30 PM
    fmt.Println(now.Format(time.RFC3339))                 // 2024-03-15T14:30:00+08:00
    fmt.Println(now.Format("20060102"))                   // 20240315

    // 解析时间字符串
    t, _ := time.Parse("2006-01-02", "2024-12-25")
    fmt.Println(t)

    // 带时区解析
    loc, _ := time.LoadLocation("Asia/Shanghai")
    t2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2024-12-25 20:00:00", loc)
    fmt.Println(t2)
}

8.2 Duration

// Duration 本质是 int64(纳秒数)
d := 5 * time.Second
fmt.Println(d)               // 5s
fmt.Println(d.Seconds())     // 5
fmt.Println(d.Milliseconds()) // 5000

// 时间计算
now := time.Now()
future := now.Add(24 * time.Hour)
past := now.Add(-2 * time.Hour)
diff := future.Sub(now)
fmt.Println("差值:", diff) // 24h0m0s

// Since 和 Until
start := time.Now()
// ... 做一些操作
elapsed := time.Since(start) // 等价于 time.Now().Sub(start)
fmt.Printf("耗时: %v\n", elapsed)

remaining := time.Until(future) // 等价于 future.Sub(time.Now())
fmt.Println("距离:", remaining)

8.3 Timer 和 Ticker

// Timer: 一次性定时器
timer := time.NewTimer(2 * time.Second)
fmt.Println("等待 2 秒...")
<-timer.C
fmt.Println("Timer 触发!")

// 取消 Timer
timer2 := time.NewTimer(5 * time.Second)
go func() {
    <-timer2.C
    fmt.Println("不会执行")
}()
timer2.Stop() // 取消

// time.After: Timer 的简写(注意循环中不要用,会泄漏)
select {
case <-time.After(1 * time.Second):
    fmt.Println("超时")
}

// Ticker: 周期性定时器
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop() // 必须 Stop,否则泄漏

count := 0
for t := range ticker.C {
    fmt.Println("Tick:", t.Format("05.000"))
    count++
    if count >= 5 {
        break
    }
}

8.4 时间比较

t1 := time.Now()
t2 := t1.Add(time.Hour)

fmt.Println(t1.Before(t2)) // true
fmt.Println(t1.After(t2))  // false
fmt.Println(t1.Equal(t1))  // true

// 零值检查
var t3 time.Time
fmt.Println(t3.IsZero()) // true

9. fmt 包格式化动词

type User struct {
    Name string
    Age  int
}

u := User{"Alice", 30}
p := &u

// 通用
fmt.Printf("%v\n", u)    // {Alice 30}           值的默认格式
fmt.Printf("%+v\n", u)   // {Name:Alice Age:30}  带字段名
fmt.Printf("%#v\n", u)   // main.User{Name:"Alice", Age:30}  Go语法表示
fmt.Printf("%T\n", u)    // main.User            类型

// 整数
fmt.Printf("%d\n", 42)   // 42         十进制
fmt.Printf("%b\n", 42)   // 101010     二进制
fmt.Printf("%o\n", 42)   // 52         八进制
fmt.Printf("%x\n", 42)   // 2a         十六进制小写
fmt.Printf("%X\n", 42)   // 2A         十六进制大写
fmt.Printf("%05d\n", 42) // 00042      前导零填充

// 浮点
fmt.Printf("%f\n", 3.14)    // 3.140000   默认
fmt.Printf("%.2f\n", 3.14)  // 3.14       精度2
fmt.Printf("%e\n", 3.14)    // 3.140000e+00  科学计数法
fmt.Printf("%g\n", 3.14)    // 3.14       紧凑表示

// 字符串
fmt.Printf("%s\n", "hello") // hello
fmt.Printf("%q\n", "hello") // "hello"    带引号
fmt.Printf("%x\n", "abc")   // 616263     十六进制

// 指针
fmt.Printf("%p\n", p)       // 0xc000010020

// 宽度与对齐
fmt.Printf("|%-10s|%10s|\n", "left", "right")
// |left      |     right|

// 错误包装(Go 1.13+, 配合 fmt.Errorf)
err := fmt.Errorf("操作失败: %w", io.EOF)
fmt.Println(err) // 操作失败: EOF

// Sprintf 返回字符串(不打印)
s := fmt.Sprintf("Name: %s, Age: %d", u.Name, u.Age)

// Fprintf 写入 Writer
fmt.Fprintf(os.Stderr, "错误: %v\n", err)

// Stringer 接口:自定义 %v 输出
// 实现 String() string 方法即可
func (u User) String() string {
    return fmt.Sprintf("%s(%d岁)", u.Name, u.Age)
}
// fmt.Println(u) -> Alice(30岁)

10. log/slog 结构化日志(Go 1.21+)

package main

import (
    "log/slog"
    "os"
)

func main() {
    // 默认文本格式
    slog.Info("服务启动", "port", 8080, "env", "production")
    // 输出: 2024/03/15 14:30:00 INFO 服务启动 port=8080 env=production

    // JSON 格式(适合日志收集系统)
    jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
        Level: slog.LevelDebug,
    })
    logger := slog.New(jsonHandler)

    logger.Debug("调试信息", "module", "auth")
    logger.Info("用户登录", "user_id", 12345, "ip", "192.168.1.1")
    logger.Warn("磁盘空间不足", "usage", "85%")
    logger.Error("数据库连接失败", "error", "connection refused", "host", "db.example.com")
    // 输出JSON: {"time":"...","level":"INFO","msg":"用户登录","user_id":12345,"ip":"192.168.1.1"}

    // 带固定字段的子 logger
    reqLogger := logger.With("request_id", "abc-123", "method", "POST")
    reqLogger.Info("处理请求")     // 自动带上 request_id 和 method
    reqLogger.Info("请求完成", "status", 200, "duration_ms", 42)

    // 分组
    logger.Info("请求详情",
        slog.Group("request",
            slog.String("method", "POST"),
            slog.String("path", "/api/users"),
        ),
        slog.Group("response",
            slog.Int("status", 200),
            slog.Int("bytes", 1234),
        ),
    )
    // JSON: {"msg":"请求详情","request":{"method":"POST","path":"/api/users"},"response":{"status":200,"bytes":1234}}

    // LogValuer 接口:自定义类型的日志输出
    // 实现 LogValue() slog.Value 方法
}

速查表

核心用途 最常用函数/类型
io I/O 抽象接口 Reader/Writer, Copy, ReadAll, Pipe
os 操作系统交互 Open/Create, ReadFile/WriteFile, Stat, Getenv, Signal
bufio 缓冲 I/O Scanner(逐行), NewReader, NewWriter
strings 字符串操作 Contains, Split, Join, Replace, Builder
bytes 字节切片操作 Buffer, Contains, Equal
strconv 类型转换 Itoa/Atoi, FormatFloat/ParseFloat
time 时间处理 Now, Parse, Format("2006-01-02"), Duration, Timer/Ticker
fmt 格式化 I/O Printf(%v/%+v/%#v/%T), Sprintf, Errorf(%w)
log/slog 结构化日志 Info/Warn/Error, With, Group, JSONHandler

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/6722

(0)
Walker的头像Walker
上一篇 11小时前
下一篇 21小时前

相关推荐

  • Go工程师体系课 020

    性能优化与 pprof 1. 先测量后优化 "Premature optimization is the root of all evil." — Donald Knuth 优化流程:1. 先写正确的代码2. 用 Benchmark 确认性能瓶颈3. 用 pprof 定位具体位置4. 优化 → 再测量 → 对比 2. pprof 工具 2.1 在 HTTP …

  • 编程基础 0013_Go企业实践案例精华

    Go 企业实践案例精华 知识来源:基于以下电子书资料整理- 《Go在百度BFE的应用 for Gopher China》- 《Go在分布式数据库中的应用》- 《Go在猎豹移动的应用》- 《Golang与高性能DSP竞价系统》- 《Go at Google: Language Design in the Service of Software Engineer…

    后端开发 20小时前
    000
  • Go工程师体系课 016

    Kubernetes 入门 —— Go 微服务部署与编排 一、Kubernetes 核心概念 1.1 什么是 Kubernetes Kubernetes(简称 K8s)是 Google 开源的容器编排平台,用于自动化部署、扩展和管理容器化应用。如果说 Docker 解决了"如何打包和运行单个容器"的问题,那么 K8s 解决的是"如何管理成百上千个容器"的问题…

  • Go工程师体系课 002

    GOPATH 与 Go Modules 的区别 1. 概念 GOPATH 是 Go 的早期依赖管理机制。 所有的 Go 项目和依赖包必须放在 GOPATH 目录中(默认是 ~/go)。 一定要设置 GO111MODULE=off 项目路径必须按照 src/包名 的结构组织。 不支持版本控制,依赖管理需要手动处理(例如 go get)。 查找依赖包的顺序是 g…

    1天前
    000
  • Go工程师体系课 014

    rocketmq 快速入门 去我们的各种配置(podman)看是怎么安装的 概念介绍 RocketMQ 是阿里开源、Apache 顶级项目的分布式消息中间件,核心组件: NameServer:服务发现与路由 Broker:消息存储、投递、拉取 Producer:消息生产者(发送消息) Consumer:消息消费者(订阅并消费消息) Topic/Tag:主题/…

    后端开发 57分钟前
    000
简体中文 繁体中文 English