編程基礎 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