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