Skip to content

Go 速查表

快速、静态类型的语言,专为简洁和并发而设计。

01

入门

Hello World

每个 Go 程序都从 package main 的 main() 函数开始。使用 'go run' 执行,'go build' 编译。gofmt 自动格式化代码(制表符、空格)。import 语句引入包 —— fmt 处理格式化 I/O。

go
package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
    fmt.Printf("Name: %s, Age: %d\n", "Alice", 30)
}

// Run: go run main.go
// Build: go build -o app main.go
// Format: gofmt -w main.go

Go 模块 (go.mod)

Go 模块 (go.mod) 自 Go 1.11 起管理依赖项。'go mod init' 创建模块文件。'go get' 添加依赖项。'go mod tidy' 移除未使用的导入并添加缺失的导入。模块路径是包的导入路径。

go
// Initialize a new module
// $ go mod init github.com/user/project

// go.mod file:
module github.com/user/project

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    golang.org/x/sync v0.3.0
)

// Add dependencies:
// $ go get github.com/gin-gonic/gin
// $ go mod tidy  // clean up unused deps

包结构

Go 以包组织代码 —— 每个目录一个包。大写名称 (Println, Add) 是导出的(公开的);小写名称是包私有的。'internal' 目录限制只能被父模块导入。包名应与目录名匹配。

go
// project structure:
// myproject/
//   go.mod
//   main.go          // package main
//   utils/
//     helper.go      // package utils
//     math.go
//   internal/
//     api/           // private (only importable within parent)

// main.go
package main

import (
    "fmt"
    "github.com/user/project/utils"
)

func main() {
    fmt.Println(utils.Add(1, 2))
}

// Exported names start with uppercase letter
// unexported names start with lowercase letter

变量与常量

使用 'var name type = value' 进行显式声明,'name := value' 进行短声明(仅限函数内)。Go 有零值:数字为 0,字符串为 '',布尔值为 false,指针/切片/map 为 nil。常量 (const) 在编译时确定,不能使用 :=。

go
package main

import "fmt"

// Package-level declarations
var version = "1.0.0" // inferred type
const Pi = 3.14159

func main() {
    // var with type
    var name string = "Alice"
    var age int = 30

    // Short declaration (only inside functions)
    city := "NYC"

    // Multiple assignment
    a, b, c := 1, 2, 3

    // Zero values (default)
    var x int      // 0
    var s string   // ""
    var ok bool    // false
    var p *int     // nil

    fmt.Println(name, age, city, a, b, c, x, s, ok, p)
}

基本类型与转换

Go 要求显式类型转换 —— 没有 C 中的隐式转换。rune 是 int32 的别名(表示 Unicode 码点)。byte 是 uint8 的别名。在数字类型之间转换可能丢失精度(float 转 int 会截断)。string(65) 将码点转换为对应字符。

go
// Numeric types
var i int = 42
var i8 int8 = 127
var u uint = 42
var f32 float32 = 3.14
var f64 float64 = 3.141592653589793

// Other types
var b bool = true
var s string = "hello"
var r rune = 'A' // int32 alias for Unicode code point
var by byte = 255 // uint8 alias

// Type conversion (explicit, no implicit conversion)
var n int = 42
var f float64 = float64(n)  // int -> float64
var s string = string(65)   // 65 -> "A" (rune to string)
var i2 int = int(f)         // float64 -> int (truncates)

// Type inference
var x = 42        // int
var y = 3.14      // float64
var z = len("hi") // int
02

字符串与格式化

字符串基础

Go 字符串是不可变的字节序列。len() 返回字节数而非字符数 —— 使用 utf8.RuneCountInString() 获取 Unicode 字符数。字符串用 ==、<、> 按字典序比较。range 遍历字符串时按 rune(Unicode 码点)迭代,正确处理多字节字符。

go
package main

import (
    "fmt"
    "strings"
)

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

    // Length (bytes, not runes!)
    fmt.Println(len(s)) // 12

    // Concatenation
    s1 := "Hello" + " " + "World"
    s2 := fmt.Sprintf("%s = %d", "age", 30)

    // Comparison
    fmt.Println("abc" == "abc") // true
    fmt.Println("a" < "b")      // true (lexicographic)

    // Iteration (by rune)
    for i, r := range "Go语" {
        fmt.Printf("%d: %c\n", i, r)
    }
}

strings 包

strings 包提供常用字符串操作。Contains/HasPrefix/HasSuffix 检查子串。Split 按分隔符拆分;Join 合并。Replace 接受计数参数(-1 表示全部)。TrimSpace 移除首尾空白。所有函数都返回新字符串(字符串不可变)。

go
import (
    "fmt"
    "strings"
)

s := "Hello, World"

// Case
strings.ToUpper(s)         // "HELLO, WORLD"
strings.ToLower(s)         // "hello, world"
strings.Title("hello world") // "Hello World"

// Search
strings.Contains(s, "World")  // true
strings.HasPrefix(s, "Hello") // true
strings.HasSuffix(s, "World") // true
strings.Index(s, "World")     // 7 (-1 if not found)
strings.Count(s, "l")         // 3

// Split & Join
parts := strings.Split("a,b,c", ",")  // ["a", "b", "c"]
joined := strings.Join(parts, "-")    // "a-b-c"

// Replace
strings.Replace(s, "l", "L", 1)  // "HeLlo, World" (1 occurrence)
strings.ReplaceAll(s, "l", "L")  // "HeLLo, WorLd"
strings.TrimSpace("  hi  ")      // "hi"

strconv 与格式化

strconv 在字符串和数字之间转换 —— 始终检查错误返回值。fmt.Printf 格式化输出:%d (int)、%f (float)、%s (string)、%t (bool)、%T (类型)、%x (十六进制)、%q (带引号)。使用 %.2f 保留 2 位小数,%05d 进行零填充。Sprintf 返回字符串而非打印。

go
import (
    "fmt"
    "strconv"
)

// String to number
n, err := strconv.Atoi("42")     // string -> int
f, err := strconv.ParseFloat("3.14", 64)

// Number to string
s1 := strconv.Itoa(42)            // int -> string
s2 := strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"

// Printf formatting
fmt.Printf("Int: %d\n", 42)
fmt.Printf("Float: %.2f\n", 3.14159)
fmt.Printf("String: %s\n", "hi")
fmt.Printf("Bool: %t\n", true)
fmt.Printf("Type: %T\n", 42)     // int
fmt.Printf("Hex: %x\n", 255)     // ff
fmt.Printf("Pad: %05d\n", 42)    // 00042
fmt.Printf("Quote: %q\n", "hi")  // "hi"

// Sprintf returns formatted string
result := fmt.Sprintf("Name: %s, Age: %d", "Alice", 30)

原始字符串与多行

原始字符串(反引号)原样保留所有内容 —— 没有转义序列,可跨多行。用于正则表达式、SQL、HTML 模板。解释字符串(双引号)处理 \n、\t 等。在循环中高效拼接字符串时,使用 strings.Builder(避免 O(n²) 分配)。

go
// Raw string literal (backticks) - no escape processing
raw := `This is a
multiline string
with \n (literal backslash-n)`

// Interpreted string (double quotes) - processes escapes
interpreted := "Line1\nLine2\tTabbed"

// Raw strings are useful for:
// - Regex patterns
// - HTML/SQL templates
// - File paths on Windows
regex := `^d{4}-d{2}-d{2}$`

// String builder (efficient concatenation)
var sb strings.Builder
for i := 0; i < 1000; i++ {
    sb.WriteString("line\n")
}
result := sb.String()

Unicode 与 Rune

Go 字符串是 UTF-8 编码的字节序列。len() 返回字节数;utf8.RuneCountInString() 返回字符数。range 遍历字符串时自动解码 UTF-8。要修改字符串,转换为 []rune,修改后再转回。这对国际化文本处理至关重要。

go
import (
    "fmt"
    "unicode/utf8"
)

s := "Hello, 世界"

// Byte length vs rune length
fmt.Println(len(s))                    // 13 (bytes)
fmt.Println(utf8.RuneCountInString(s)) // 9 (runes/characters)

// Decode runes manually
for i := 0; i < len(s); {
    r, size := utf8.DecodeRuneInString(s[i:])
    fmt.Printf("%c ", r)
    i += size
}

// Range automatically decodes runes
for i, r := range s {
    fmt.Printf("%d:%c ", i, r)
}

// Rune slice (mutable)
runes := []rune("Hello")
runes[0] = 'J'
s2 := string(runes) // "Jello"
03

控制流

If / Else

Go 的 if 不需要在条件周围加括号,但花括号是必需的(即使是单行体)。初始化语句 (if x := f(); x > 0) 的作用域限于 if/else 块 —— 常用于错误检查。此模式使变量作用域保持紧凑。

go
// Basic if/else
score := 85
if score >= 90 {
    fmt.Println("A")
} else if score >= 80 {
    fmt.Println("B")
} else {
    fmt.Println("C")
}

// If with initialization statement
if n := computeValue(); n > 100 {
    fmt.Println("big:", n)
} else {
    fmt.Println("small:", n)
}
// n is not accessible here (scoped to if)

// No parentheses needed, but braces are required

For 循环

Go 只有一个循环关键字:'for'。它处理 C 风格、while 风格 (for cond)、无限循环 (for) 和迭代 (for range)。range 适用于切片、map、字符串和通道。使用 _ 跳过索引或值。map 迭代顺序按设计是随机的。

go
// C-style for
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// While-style (condition only)
n := 10
for n > 0 {
    n--
}

// Infinite loop
for {
    break
}

// Range (iterate slices, maps, strings)
nums := []int{10, 20, 30}
for index, value := range nums {
    fmt.Printf("%d: %d\n", index, value)
}

// Skip index or value with _
for _, value := range nums {
    fmt.Println(value)
}

// Range over map (unordered!)
m := map[string]int{"a": 1, "b": 2}
for key, val := range m {
    fmt.Println(key, val)
}

Switch

Go 的 switch 默认不穿透(不同于 C/Java)—— 每个 case 是独立分支。使用 fallthrough 强制穿透。每个 case 可用逗号匹配多个值。无表达式的 switch 作为更清晰的 if/else 链。带初始化语句的 switch 将变量作用域限制在 switch 内。

go
// Basic switch
day := 3
switch day {
case 1:
    fmt.Println("Mon")
case 2, 3, 4, 5:
    fmt.Println("Weekday")
case 6, 7:
    fmt.Println("Weekend")
default:
    fmt.Println("Invalid")
}

// Switch with no expression (like if/else chain)
switch {
case score >= 90:
    grade = "A"
case score >= 80:
    grade = "B"
default:
    grade = "C"
}

// Switch with init
switch os := runtime.GOOS; os {
case "linux":
    fmt.Println("Linux")
case "darwin":
    fmt.Println("macOS")
}

// Fallthrough (rare, goes to next case unconditionally)
switch 1 {
case 1:
    fmt.Println("one")
    fallthrough
case 2:
    fmt.Println("two") // executes even though value is 1
}

Defer

defer 调度一个函数调用在 surrounding 函数返回时执行 —— 后进先出 (LIFO)。用于清理(关闭文件、释放锁、关闭连接)。延迟调用的参数会立即求值,但调用在返回时执行。即使函数 panic,defer 也会运行。

go
// Defer runs when function returns (LIFO order)
func main() {
    defer fmt.Println("third")
    defer fmt.Println("second")
    fmt.Println("first")
}
// Output: first, second, third

// Common: resource cleanup
func readFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close() // runs when readFile returns

    // ... use f ...
    return nil
}

// Defer evaluates arguments immediately
i := 1
defer fmt.Println(i) // prints 1 (not 2)
i = 2

Goto、Break、Continue

break 退出最内层循环;continue 跳到下一次迭代。标签 (break outer) 退出嵌套循环。goto 存在但很少使用 —— 优先使用结构化控制流。标签也可与 continue 一起使用,跳到外层循环的下一次迭代。

go
// Break and continue
for i := 0; i < 10; i++ {
    if i%2 == 0 {
        continue // skip even numbers
    }
    if i > 7 {
        break // exit loop
    }
    fmt.Println(i)
}

// Break with label (for nested loops)
outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if i == 1 && j == 1 {
            break outer // exits both loops
        }
    }
}

// Goto (rarely used, avoid)
i := 0
loop:
    if i < 5 {
        fmt.Println(i)
        i++
        goto loop
    }

Select(通道操作)

select 类似于通道的 switch —— 它等待多个通道操作并选择第一个就绪的。default case 使其非阻塞。在循环中使用 select 实现事件驱动模式。time.After() 创建超时通道。如果多个 case 同时就绪,select 随机选择,防止饥饿。

go
// Select waits on multiple channel operations
select {
case msg := <-messages:
    fmt.Println("received:", msg)
case <-timeout:
    fmt.Println("timed out")
default:
    fmt.Println("no activity") // non-blocking
}

// Select with send
ch1 := make(chan int)
ch2 := make(chan int)
go func() { ch1 <- 1 }()
go func() { ch2 <- 2 }()

select {
case v := <-ch1:
    fmt.Println("ch1:", v)
case v := <-ch2:
    fmt.Println("ch2:", v)
}

// Select in a loop (event loop pattern)
for {
    select {
    case msg := <-ch:
        fmt.Println(msg)
    case <-time.After(5 * time.Second):
        return // timeout after 5s of inactivity
    }
}
04

函数

定义与多返回值

Go 函数可返回多个值 —— 规范模式是 (result, error)。命名返回值作为已声明的变量初始化为零值;'裸' return 使用它们。命名返回值能提高复杂函数的可读性,但过度使用可能造成困惑。始终立即检查错误。

go
// Function with multiple return values
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("divide by zero")
    }
    return a / b, nil
}

// Named return values
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return // "naked" return (uses named values)
}

// Usage
result, err := divide(10, 2)
if err != nil {
    log.Fatal(err)
}
fmt.Println(result) // 5

x, y := split(100)
fmt.Println(x, y)

可变参数与闭包

可变参数函数 (...T) 接受任意数量的参数,作为切片接收。用 ... 展开切片。闭包通过引用从其外围作用域捕获变量 —— counter() 函数返回一个记住 'count' 的闭包。闭包适用于回调、迭代器和有状态函数。

go
// Variadic function (variable arguments)
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}
fmt.Println(sum(1, 2, 3))       // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15

// Spread a slice
nums := []int{1, 2, 3}
fmt.Println(sum(nums...)) // 6

// Closure (function that captures variables)
func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

next := counter()
fmt.Println(next()) // 1
fmt.Println(next()) // 2
fmt.Println(next()) // 3

函数作为值

Go 中的函数是一等值 —— 可以赋值给变量、作为参数传递、存储在数据结构中。用 'type Name func(params) returns' 定义函数类型。函数 map 适用于分发表、命令处理程序和策略模式。

go
// Functions are first-class values
func apply(f func(int) int, x int) int {
    return f(x)
}

func double(x int) int { return x * 2 }
func square(x int) int { return x * x }

fmt.Println(apply(double, 5)) // 10
fmt.Println(apply(square, 5)) // 25

// Function type
type MathFunc func(int) int

var fn MathFunc = func(x int) int { return x + 1 }
fmt.Println(fn(10)) // 11

// Map of functions
ops := map[string]func(int, int) int{
    "add": func(a, b int) int { return a + b },
    "sub": func(a, b int) int { return a - b },
    "mul": func(a, b int) int { return a * b },
}
fmt.Println(ops["add"](3, 4)) // 7

init() 与匿名函数

init() 函数在 main() 之前自动运行,按声明顺序执行。用于设置(配置加载、验证、注册)。每个文件允许多个 init() 函数。匿名函数可立即调用 (IIFE) 或赋值给变量。它们也用于 goroutine。

go
// init() runs before main(), once per package
// Multiple init() functions allowed, run in order declared
package main

import "fmt"

var config string

func init() {
    // Setup code: config, connections, validation
    config = "production"
    fmt.Println("init 1")
}

func init() {
    fmt.Println("init 2, config:", config)
}

func main() {
    fmt.Println("main")
}
// Output: init 1, init 2, main

// Anonymous function (IIFE)
result := func(x int) int {
    return x * 2
}(5) // immediately invoked, result = 10

// Goroutine with anonymous function
go func(msg string) {
    fmt.Println(msg)
}("async")

泛型 (Go 1.18+)

Go 1.18+ 支持带类型参数 [T any] 的泛型。类型约束(接口)限制允许的类型 —— 使用 'any' 表示无约束,'comparable' 用于 == / != 运算符。联合约束 (int | float64) 允许特定类型。泛型支持类型安全的可复用数据结构和算法,无需代码重复。

go
// Generic function with type parameter
func Map[T, U any](slice []T, f func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = f(v)
    }
    return result
}

nums := []int{1, 2, 3}
doubled := Map(nums, func(n int) int { return n * 2 })
// [2, 4, 6]

strs := Map(nums, func(n int) string {
    return fmt.Sprintf("num%d", n)
})
// ["num1", "num2", "num3"]

// Type constraints
type Number interface {
    int | int64 | float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

// comparable constraint (for maps/slices keys)
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

方法

方法是带接收者参数的函数。值接收者 (r Rectangle) 在副本上操作 —— 无法修改原始值。指针接收者 (r *Rectangle) 可修改且避免复制大型结构体。一致性很重要:如果一个方法使用指针接收者,所有方法都应如此。方法可在同一包中的任何类型上定义。

go
type Rectangle struct {
    Width, Height float64
}

// Method with value receiver
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// Method with pointer receiver (can modify)
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

// Method on non-struct types
type MyString string

func (s MyString) Shout() string {
    return strings.ToUpper(string(s)) + "!"
}

// Usage
r := Rectangle{Width: 10, Height: 5}
fmt.Println(r.Area()) // 50
r.Scale(2)
fmt.Println(r.Width)  // 20

s := MyString("hello")
fmt.Println(s.Shout()) // HELLO!
05

数据结构

数组与切片

数组长度固定;切片是动态的(基于数组)。append() 添加元素,按需增长容量。make([]T, len, cap) 预分配以提高效率。切片创建视图(共享底层数组)—— 使用 copy() 获得独立数据。优化时始终检查 len/cap。

go
// Array (fixed length, rarely used directly)
var arr [3]int = [3]int{1, 2, 3}
arr2 := [...]int{4, 5, 6} // size inferred

// Slice (dynamic array, most common)
nums := []int{1, 2, 3}
nums = append(nums, 4)        // [1, 2, 3, 4]
nums = append(nums, 5, 6, 7) // [1, 2, 3, 4, 5, 6, 7]

// Make a slice with capacity
s := make([]int, 3, 10) // len=3, cap=10

// Slicing
sub := nums[1:4]  // [2, 3, 4]
first := nums[:2] // [1, 2]
last := nums[3:]  // [4, 5, 6, 7]

// Copy
dst := make([]int, len(nums))
copy(dst, nums)

// Length and capacity
fmt.Println(len(nums), cap(nums))

Map

map 是 Go 的哈希表 —— 无序的键/值对。comma-ok 模式 (val, ok := m[key]) 检查键是否存在。delete() 移除键。map 迭代顺序按设计是随机的。map 是引用类型 —— 传递给函数会共享底层数据。nil map 不能写入(使用 make())。

go
// Create a map
m := map[string]int{
    "Alice": 30,
    "Bob":   25,
}

// Using make
ages := make(map[string]int)
ages["Charlie"] = 35

// Access
fmt.Println(m["Alice"]) // 30

// Check existence (comma-ok pattern)
age, ok := m["David"]
if !ok {
    fmt.Println("not found")
}

// Delete
delete(m, "Bob")

// Iterate (random order)
for name, age := range m {
    fmt.Printf("%s: %d\n", name, age)
}

// Length
fmt.Println(len(m))

// Nested maps
matrix := map[string]map[string]int{}
matrix["row1"] = map[string]int{"col1": 1}

结构体

结构体将相关字段分组。使用命名初始化 (Person{Name: ...}) 以提高清晰度。new() 返回带零值的指针。匿名结构体适用于一次性数据形状。结构体嵌入(无字段名)会提升被嵌入结构体的字段和方法 —— Go 中继承的替代方案。

go
// Define a struct
type Person struct {
    Name string
    Age  int
    Address string // field names are exported (capitalized)
}

// Create instances
p1 := Person{Name: "Alice", Age: 30}
p2 := Person{"Bob", 25, "NYC"} // positional (not recommended)
p3 := new(Person)              // returns *Person, zero values
p3.Name = "Charlie"

// Anonymous struct (one-off)
config := struct {
    Port int
    Host string
}{
    Port: 8080,
    Host: "localhost",
}

// Struct embedding (composition)
type Employee struct {
    Person          // embedded (promotes fields)
    Salary   float64
}

emp := Employee{
    Person: Person{Name: "Dave", Age: 40},
    Salary: 50000,
}
fmt.Println(emp.Name) // "Dave" (promoted from Person)

指针

Go 指针 (*T) 持有内存地址。& 取地址,* 解引用。不同于 C,Go 没有指针运算(更安全)。结构体指针允许简写 (u.Name 而非 (*u).Name)。nil 指针在解引用时会导致 panic。Go 有垃圾回收 —— 无需手动 free()。

go
// Pointer basics
x := 42
p := &x          // p is *int, points to x
fmt.Println(*p)  // 42 (dereference)
*p = 100         // modify x through pointer
fmt.Println(x)   // 100

// new() allocates and returns pointer
p2 := new(int)   // *int, value 0
*p2 = 42

// Pointers to structs
type User struct{ Name string }
u := &User{Name: "Alice"} // *User
u.Name = "Bob"            // (*u).Name shorthand
fmt.Println(u.Name)       // Bob

// nil pointer
var p3 *int // nil
// *p3 // panic: nil pointer dereference

// No pointer arithmetic (unlike C)
// p++ is illegal

切片操作

Go 没有内置的 filter/map/reduce —— 自己编写或使用 slices 包 (Go 1.21+)。插入/删除需要用 append+copy 移动元素。注意切片别名:s[:i] 和 s[i+1:] 共享底层数组。对于二维切片,需分别分配每一行。

go
// Filter
func filter(nums []int, pred func(int) bool) []int {
    result := []int{}
    for _, n := range nums {
        if pred(n) {
            result = append(result, n)
        }
    }
    return result
}

evens := filter([]int{1,2,3,4,5}, func(n int) bool {
    return n%2 == 0
}) // [2, 4]

// Insert at index
func insert(s []int, i, v int) []int {
    s = append(s, 0)
    copy(s[i+1:], s[i:])
    s[i] = v
    return s
}

// Remove at index
func remove(s []int, i int) []int {
    return append(s[:i], s[i+1:]...)
}

// 2D slice
matrix := make([][]int, 3)
for i := range matrix {
    matrix[i] = make([]int, 4)
}

排序与搜索

sort.Ints/Strings/Float64s 原地排序。sort.Slice 配合比较器处理自定义类型。sort.Search* 对已排序切片进行二分搜索。Go 1.21+ 添加了带泛型排序函数的 slices 包。所有排序都是原地的 —— 如需保留原始顺序,请先复制。

go
import "sort"

// Sort a slice
nums := []int{3, 1, 4, 1, 5, 9, 2, 6}
sort.Ints(nums) // [1 1 2 3 4 5 6 9]
fmt.Println(nums)

// Sort strings
strs := []string{"banana", "apple", "cherry"}
sort.Strings(strs) // [apple banana cherry]

// Sort with custom comparator
type Person struct {
    Name string
    Age  int
}

people := []Person{
    {"Alice", 30},
    {"Bob", 25},
    {"Charlie", 35},
}

sort.Slice(people, func(i, j int) bool {
    return people[i].Age < people[j].Age // by age ascending
})

// Binary search (sorted slice)
idx := sort.SearchInts(nums, 4) // index of 4

// Sort package (Go 1.21+): slices.Sort
import "slices"
slices.Sort(people) // needs Less method or cmp func
06

方法与接口

定义接口

接口定义方法签名。Go 使用结构化类型 —— 如果类型拥有所有必需的方法,则自动实现接口(无需显式 'implements' 声明)。这实现了解耦设计:在使用处定义接口,而非在实现处。

go
// Interface: a set of method signatures
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Implement implicitly (no 'implements' keyword)
type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

// Use the interface
func describe(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n",
        s.Area(), s.Perimeter())
}

c := Circle{Radius: 5}
describe(c) // works! Circle implements Shape

空接口与类型断言

interface{}(或 Go 1.18+ 中的 'any')可持有任何值。类型断言 (v.(T)) 提取具体类型 —— 类型错误时会 panic,因此使用 comma-ok 模式。类型 switch (switch v.(type)) 优雅处理多种类型。空接口适用于通用容器但失去类型安全 —— 优先使用泛型。

go
// Empty interface (any type)
// Go 1.18+: 'any' is an alias for interface{}
func printAny(v any) {
    fmt.Println(v)
}

printAny(42)
printAny("hello")
printAny([]int{1, 2, 3})

// Type assertion
var i any = "hello"
s := i.(string)    // panics if not string
fmt.Println(s)

// Safe type assertion (comma-ok)
s, ok := i.(string)
if ok {
    fmt.Println("string:", s)
}

// Type switch
func describe(v any) {
    switch x := v.(type) {
    case int:
        fmt.Printf("int: %d\n", x)
    case string:
        fmt.Printf("string: %s\n", x)
    case []int:
        fmt.Printf("int slice: %v\n", x)
    default:
        fmt.Printf("unknown type: %T\n", x)
    }
}

接口组合

接口可以嵌入其他接口(组合)。io.Reader 和 io.Writer 是 Go 最重要的接口 —— 由文件、网络连接、缓冲区等实现。这实现了强大的抽象:接受 io.Reader 的函数适用于任何可读源。小型、专注的接口(1-3 个方法)是惯用法。

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

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

// ReadWriter combines Reader and Writer
type ReadWriter interface {
    Reader
    Writer
}

// io.Reader and io.Writer are built-in interfaces
// Many types implement them: *os.File, *bytes.Buffer, net.Conn

func copyData(r io.Reader, w io.Writer) error {
    buf := make([]byte, 1024)
    for {
        n, err := r.Read(buf)
        if n > 0 {
            if _, err := w.Write(buf[:n]); err != nil {
                return err
            }
        }
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }
    }
    return nil
}

指针接收者 vs 值接收者

指针接收者可修改结构体并避免复制大值。值接收者是安全的(无法修改)且允许在值和指针上调用方法。如果任何方法使用指针接收者,所有方法都应如此(保持一致性)。指针接收者方法对 T 和 *T 都满足接口;值接收者方法仅对 T 满足。

go
type Counter struct {
    count int
}

// Value receiver: works on a copy, can't modify
func (c Counter) Get() int {
    return c.count
}

// Pointer receiver: can modify the original
func (c *Counter) Increment() {
    c.count++
}

c := Counter{}
c.Increment() // Go auto-takes address (&c).Increment()
c.Increment()
fmt.Println(c.Get()) // 2

// Interface implementation note:
// *Counter implements both Get() and Increment()
// Counter (value) implements only Get()
// So *Counter satisfies an interface requiring both

Stringer 接口

Stringer 接口 (String() string) 控制类型在 Print/Printf 中的显示方式 —— 类似 Java/JS 中的 toString()。%v 使用 String();%+v 显示字段名;%#v 显示 Go 语法。error 接口工作方式相同:实现 Error() string 使任何类型成为 error。这些是 Go 最常见的内置接口。

go
// Stringer interface (like toString() in other languages)
type Stringer interface {
    String() string
}

type Person struct {
    Name string
    Age  int
}

// Implement Stringer
func (p Person) String() string {
    return fmt.Sprintf("%s (%d years)", p.Name, p.Age)
}

p := Person{Name: "Alice", Age: 30}
fmt.Println(p)       // Alice (30 years) — uses String()
fmt.Printf("%v\n", p) // Alice (30 years)
fmt.Printf("%+v\n", p) // Name:Alice Age:30
fmt.Printf("%#v\n", p) // main.Person{Name:"Alice", Age:30}

// Error interface is similar:
// type error interface { Error() string }
// Implement Error() to make a type usable as an error

类型嵌入与组合

Go 使用组合而非继承。嵌入结构体会将其字段和方法提升到外部结构体。可以通过在外部类型上定义同名方法来覆盖被提升的方法。嵌入接口允许装饰/委托 —— 外部类型满足接口并可转发调用。

go
// Embed a struct (composition over inheritance)
type Animal struct {
    Name string
}

func (a Animal) Speak() string {
    return a.Name + " makes a sound"
}

type Dog struct {
    Animal // embedded — promotes Name and Speak()
    Breed  string
}

d := Dog{
    Animal: Animal{Name: "Rex"},
    Breed:  "Labrador",
}

fmt.Println(d.Name)      // "Rex" (promoted)
fmt.Println(d.Speak())   // "Rex makes a sound" (promoted)
fmt.Println(d.Animal.Name) // explicit access also works

// Override a promoted method
func (d Dog) Speak() string {
    return d.Name + " barks!"
}
fmt.Println(d.Speak()) // "Rex barks!"

// Embed an interface
type Logger struct {
    io.Writer // embed interface
}
// Logger now has Write() method, delegating to the embedded Writer
07

错误处理

错误基础

Go 将错误作为值处理,而非异常。函数返回 (result, error) —— 始终立即检查 err != nil。errors.New() 创建简单错误;fmt.Errorf() 添加格式化。哨兵错误 (var ErrX = errors.New()) 支持用 == 比较。切勿忽略错误(仅在有意时使用 _)。

go
// Functions return errors as last return value
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("cannot divide by zero")
    }
    return a / b, nil
}

// Always check errors immediately
result, err := divide(10, 0)
if err != nil {
    log.Fatal(err) // or handle gracefully
}
fmt.Println(result)

// fmt.Errorf for formatted errors
func validate(age int) error {
    if age < 0 {
        return fmt.Errorf("invalid age: %d (must be positive)", age)
    }
    return nil
}

// Sentinel errors
var ErrNotFound = errors.New("not found")
if err := lookup("key"); err == ErrNotFound {
    // handle not found
}

自定义错误类型

自定义错误类型实现 error 接口 (Error() string)。它们携带结构化数据(字段、代码、上下文),超越简单的消息。使用类型断言 (*ValidationError) 访问数据。这对领域特定的错误处理至关重要 —— 例如 HTTP 状态码、验证详情、重试逻辑。

go
// Custom error type (implements error interface)
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on '%s': %s", e.Field, e.Message)
}

// Use the custom error
func validateUser(name string) error {
    if name == "" {
        return &ValidationError{
            Field:   "name",
            Message: "cannot be empty",
        }
    }
    return nil
}

// Type-assert to access fields
err := validateUser("")
if ve, ok := err.(*ValidationError); ok {
    fmt.Println("Field:", ve.Field)   // "name"
    fmt.Println("Message:", ve.Message) // "cannot be empty"
}

errors.Is 与 errors.As (Go 1.13+)

Go 1.13+ 添加了用 %w 进行错误包装 (fmt.Errorf)。errors.Is() 检查错误是否匹配哨兵错误(解包链)。errors.As() 从链中提取特定错误类型。这实现了分层错误处理:底层错误被上下文包装,在高层检查。始终用 %w(而非 %v)包装以保留链。

go
import "errors"

// Wrapped errors with %w
var ErrNotFound = errors.New("not found")

func getUser(id int) error {
    if id == 0 {
        return fmt.Errorf("getUser(%d): %w", id, ErrNotFound)
    }
    return nil
}

// errors.Is: check if error matches (unwraps the chain)
err := getUser(0)
if errors.Is(err, ErrNotFound) {
    fmt.Println("user not found")
}

// errors.As: extract a specific error type
var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println("field:", ve.Field)
}

// errors.Unwrap: get the wrapped error
inner := errors.Unwrap(err)

Panic 与 Recover

panic() 用于不可恢复的错误(bug、不变量违反)—— 不用于正常错误处理。recover() 捕获 panic 但仅在延迟函数中有效。在以下情况使用 panic/recover:编程错误(索引越界)、包初始化失败、保护 goroutine 不使程序崩溃。对于预期失败,优先返回错误。

go
// Panic: unrecoverable error (like throw)
func mustParse(s string) int {
    n, err := strconv.Atoi(s)
    if err != nil {
        panic(fmt.Sprintf("invalid number: %s", s))
    }
    return n
}

// Recover: catch a panic (only in deferred functions)
func safeCall() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recovered:", r)
        }
    }()
    panic("something went wrong")
}

// Practical: safe goroutine
func safeGo(fn func()) {
    go func() {
        defer func() {
            if r := recover(); r != nil {
                log.Println("goroutine panic:", r)
            }
        }()
        fn()
    }()
}

错误包装模式

在每一层使用 fmt.Errorf 配合 %w 包装错误并添加上下文。这创建错误链:顶层看到完整路径 (getUserProfile → fetchUser → sql error)。上下文(函数名、参数)有助于调试。使用 errors.Is/As 检查链中的特定错误。避免用相同上下文多次包装同一错误。

go
// Wrap errors with context as they propagate up
func fetchUser(id int) (*User, error) {
    row := db.QueryRow("SELECT ... WHERE id = ?", id)
    var u User
    if err := row.Scan(&u.Name, &u.Age); err != nil {
        return nil, fmt.Errorf("fetchUser(%d): %w", id, err)
    }
    return &u, nil
}

func getUserProfile(id int) (*Profile, error) {
    user, err := fetchUser(id)
    if err != nil {
        return nil, fmt.Errorf("getUserProfile(%d): %w", id, err)
    }
    // ...
}

// At the top level, log the full chain
profile, err := getUserProfile(42)
if err != nil {
    log.Printf("error: %v", err)
    // Output: getUserProfile(42): fetchUser(42): sql: no rows
}

// golang.org/x/xerrors or pkg/errors for stack traces

Defer、Panic、Recover 组合使用

组合 defer(清理)、panic(致命错误)和 recover(捕获 panic)实现健壮的资源管理。defer 按 LIFO 顺序执行,即使在 panic 期间。命名返回值 (err error) 可在延迟的 recover 中设置。此模式确保文件/连接被关闭、事务被回滚,即使代码 panic。

go
// Complete pattern: cleanup + panic recovery
func processFile(path string) (err error) {
    file, e := os.Open(path)
    if e != nil {
        return e
    }
    defer func() {
        // Close file regardless of panic
        file.Close()

        // Recover from panic and convert to error
        if r := recover(); r != nil {
            err = fmt.Errorf("panic in processFile: %v", r)
        }
    }()

    // Do work that might panic
    data := parseFile(file) // might panic
    return saveData(data)
}

// Defer for resource cleanup (always runs)
func withDatabase(db *sql.DB, fn func(*sql.DB) error) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer func() {
        if err != nil {
            tx.Rollback()
        } else {
            tx.Commit()
        }
    }()
    return fn(tx)
}
08

并发

Goroutine

Goroutine 是 Go 的轻量级线程 —— 用 'go' 启动。它们很廉价(约 2KB 栈)并由 Go 运行时调度器管理(M:N 调度)。main 函数不会等待 goroutine —— 使用 sync.WaitGroup 或通道同步。生产环境切勿使用 time.Sleep 同步(使用 WaitGroup)。

go
package main

import (
    "fmt"
    "time"
)

func sayHello(name string) {
    for i := 0; i < 3; i++ {
        fmt.Println(name, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    // Launch goroutine with 'go' keyword
    go sayHello("Alice")
    go sayHello("Bob")

    // Anonymous goroutine
    go func() {
        fmt.Println("anonymous goroutine")
    }()

    // Wait for goroutines (simple approach)
    time.Sleep(1 * time.Second)
    fmt.Println("done")
}

// Goroutines are lightweight (~2KB stack, grows as needed)
// Millions of goroutines can run concurrently

通道

通道是 goroutine 通信的类型化管道。无缓冲 (make(chan T)) 阻塞直到发送方和接收方都就绪(同步)。有缓冲 (make(chan T, n)) 仅在满时阻塞(异步)。发送方应关闭通道,接收方不应。range 遍历通道直到关闭。通道实现'通过通信共享内存'。

go
// Unbuffered channel (synchronous)
ch := make(chan string)

// Send and receive
go func() {
    ch <- "hello" // blocks until received
}()
msg := <-ch // blocks until sent
fmt.Println(msg)

// Buffered channel (asynchronous)
buf := make(chan int, 3)
buf <- 1 // doesn't block (buffer has space)
buf <- 2
buf <- 3
// buf <- 4 // would block (buffer full)
fmt.Println(<-buf) // 1

// Close a channel (sender closes, never receiver)
close(buf)

// Range over channel (until closed)
for v := range buf {
    fmt.Println(v)
}

// Check if closed
v, ok := <-buf
if !ok {
    fmt.Println("channel closed")
}

Select 语句

select 让 goroutine 等待多个通道操作 —— 选择第一个就绪的(多个就绪时随机选择)。default case 使其非阻塞。time.After() 创建超时通道。select 是并发 Go 的核心:事件循环、扇入/扇出、超时。始终包含超时以避免死锁。

go
// Select: multiplex channel operations
ch1 := make(chan string)
ch2 := make(chan string)

go func() {
    time.Sleep(1 * time.Second)
    ch1 <- "one"
}()
go func() {
    time.Sleep(2 * time.Second)
    ch2 <- "two"
}()

// Wait for first to arrive
for i := 0; i < 2; i++ {
    select {
    case msg1 := <-ch1:
        fmt.Println("received:", msg1)
    case msg2 := <-ch2:
        fmt.Println("received:", msg2)
    }
}

// Timeout with select
select {
case result := <-slowOperation():
    fmt.Println(result)
case <-time.After(3 * time.Second):
    fmt.Println("timeout!")
}

// Non-blocking receive (default case)
select {
case msg := <-ch:
    fmt.Println(msg)
default:
    fmt.Println("no message") // runs if no data
}

sync.WaitGroup

sync.WaitGroup 等待一组 goroutine 完成。Add(n) 增加计数器,Done() 递减(使用 defer),Wait() 阻塞直到为零。始终将循环变量作为参数传递给 goroutine,以避免闭包捕获 bug(Go 1.22 已修复但仍推荐)。对于即发即忘的并发,WaitGroup 比通道更简单。

go
import "sync"

func main() {
    var wg sync.WaitGroup

    // Launch 5 goroutines
    for i := 0; i < 5; i++ {
        wg.Add(1) // increment counter
        go func(id int) {
            defer wg.Done() // decrement when done
            fmt.Printf("Worker %d started\n", id)
            time.Sleep(time.Duration(id) * 100 * time.Millisecond)
            fmt.Printf("Worker %d done\n", id)
        }(i)
    }

    wg.Wait() // block until all Done() called
    fmt.Println("all workers finished")
}

// Common mistake: passing loop variable to goroutine
// Always pass as parameter: go func(i int) { ... }(i)
// (Go 1.22+ fixes loop variable scoping, but still good practice)

Mutex 与 Sync

sync.Mutex 保护共享状态免受并发访问 —— Lock/Unlock 配合 defer。RWMutex 允许多个读取者或一个写入者(更适合读密集型工作负载)。sync.Once 确保初始化只发生一次(单例模式)。优先用通道通信,用互斥锁保护共享状态。'通过通信共享内存;不要通过共享内存通信。'

go
import "sync"

// Mutex: mutual exclusion lock
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock() // always unlock with defer
    c.count++
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

// RWMutex: multiple readers, one writer
type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()         // read lock (multiple allowed)
    defer c.mu.RUnlock()
    val, ok := c.data[key]
    return val, ok
}

func (c *Cache) Set(key, val string) {
    c.mu.Lock()          // write lock (exclusive)
    defer c.mu.Unlock()
    c.data[key] = val
}

// sync.Once: run exactly once
var (
    once sync.Once
    instance *Database
)

func GetDB() *Database {
    once.Do(func() {
        instance = &Database{}
    })
    return instance
}

并发模式

工作池:固定数量的 goroutine 从通道处理作业,将结果发送到另一通道。这限制并发并防止资源耗尽。扇出/扇入:将工作分配给多个 goroutine,然后合并结果。关键洞察:当不再发送数据时关闭通道,使 range 循环终止。这些模式是并发 Go 的基础。

go
// Worker pool
func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        time.Sleep(time.Second) // simulate work
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    // Start 3 workers
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // Send 5 jobs
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)

    // Collect results
    for a := 1; a <= 5; a++ {
        fmt.Println(<-results)
    }
}

// Fan-out/Fan-in
func fanOut(input <-chan int, workers int) <-chan int {
    output := make(chan int)
    var wg sync.WaitGroup
    wg.Add(workers)
    for i := 0; i < workers; i++ {
        go func() {
            defer wg.Done()
            for v := range input {
                output <- v * 2
            }
        }()
    }
    go func() { wg.Wait(); close(output) }()
    return output
}
09

文件 I/O 与 OS

读取文件

os.ReadFile() 将整个文件读入内存(简单但不适用于大文件)。对于大文件,用 os.Open() 打开,分块读取,并始终 defer Close()。bufio.Scanner 按行读取 —— 非常适合文本处理。始终检查错误,特别是 io.EOF 以了解读取何时完成。

go
import (
    "io"
    "os"
)

// Read entire file (small files)
data, err := os.ReadFile("input.txt")
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(data))

// Open and read (large files)
file, err := os.Open("large.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

// Read in chunks
buf := make([]byte, 1024)
for {
    n, err := file.Read(buf)
    if n > 0 {
        // process buf[:n]
    }
    if err == io.EOF {
        break
    }
    if err != nil {
        log.Fatal(err)
    }
}

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

写入文件

os.WriteFile() 原子性地创建/截断并写入(简单)。带标志的 os.OpenFile() 提供控制:O_APPEND(追加到末尾)、O_CREATE(不存在则创建)、O_TRUNC(截断)。使用 bufio.Writer 进行多次小写入(在内存中缓冲,最后 flush)。文件模式 0644:所有者可读写,其他人可读。

go
// Write entire file (creates or truncates)
err := os.WriteFile("output.txt", []byte("Hello, World!"), 0644)
if err != nil {
    log.Fatal(err)
}

// Open for writing (with options)
file, err := os.OpenFile("log.txt",
    os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
    log.Fatal(err)
}
defer file.Close()

// Write
if _, err := file.WriteString("log entry\n"); err != nil {
    log.Fatal(err)
}

// Buffered writer (efficient for many small writes)
writer := bufio.NewWriter(file)
for i := 0; i < 1000; i++ {
    writer.WriteString(fmt.Sprintf("line %d\n", i))
}
writer.Flush() // don't forget to flush!

// File modes: 0644 = owner read/write, others read
// os.O_APPEND, O_CREATE, O_WRONLY, O_RDONLY, O_RDWR, O_TRUNC

文件操作

os.Mkdir/MkdirAll 创建目录。os.ReadDir 高效列出条目。filepath.WalkDir 递归遍历目录。os.Stat 提供文件信息(大小、修改时间、权限)。os.IsNotExist 检查文件是否缺失。os.Remove 删除单个文件;RemoveAll 递归删除目录。始终检查错误。

go
// Create directory
os.Mkdir("mydir", 0755)       // one level
os.MkdirAll("a/b/c", 0755)    // nested directories

// List directory
entries, err := os.ReadDir(".")
if err != nil {
    log.Fatal(err)
}
for _, entry := range entries {
    fmt.Println(entry.Name(), entry.IsDir())
}

// Walk directory tree
filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
    if err != nil {
        return err
    }
    fmt.Println(path)
    return nil
})

// File info
info, _ := os.Stat("file.txt")
fmt.Println(info.Size())      // bytes
fmt.Println(info.ModTime())   // last modified
fmt.Println(info.IsDir())

// Check existence
if _, err := os.Stat("file.txt"); os.IsNotExist(err) {
    fmt.Println("file does not exist")
}

// Remove
os.Remove("file.txt")
os.RemoveAll("directory") // recursive

环境变量与命令行

os.Getenv/LookupEnv/Setenv 管理环境变量。os.Args 提供原始命令行参数。flag 包提供带默认值和帮助文本的解析标志。LookupEnv 区分未设置和空值。环境变量是配置 12-factor 应用的标准方式(API 密钥、数据库 URL)。

go
// Environment variables
os.Setenv("API_KEY", "secret")
key := os.Getenv("API_KEY") // "" if not set

// With existence check
if val, ok := os.LookupEnv("HOME"); ok {
    fmt.Println("HOME:", val)
}

// All environment variables
for _, e := range os.Environ() {
    fmt.Println(e)
}

// Command line arguments
// args := os.Args // []string, args[0] is program name

// Using flag package
var port int
var host string
flag.IntVar(&port, "port", 8080, "server port")
flag.StringVar(&host, "host", "localhost", "server host")
flag.Parse()
fmt.Printf("Running on %s:%d\n", host, port)

// Run: go run main.go -port 3000 -host 0.0.0.0

执行命令

os/exec 运行外部命令。Command() 创建命令;Output() 捕获 stdout;Run() 用自定义 Stdout/Stderr 执行。使用 CommandContext 实现超时(杀死进程)。始终检查错误 —— exec.ExitError 表示非零退出码。注意用户输入以防止命令注入。

go
import "os/exec"

// Run a command and get output
cmd := exec.Command("ls", "-la", "/tmp")
output, err := cmd.Output()
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(output))

// Capture stdout and stderr separately
cmd = exec.Command("git", "status")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err = cmd.Run()
fmt.Println("stdout:", stdout.String())
fmt.Println("stderr:", stderr.String())

// Pipe input to command
cmd = exec.Command("grep", "error")
cmd.Stdin = strings.NewReader("info\nerror\nwarn\n")
result, _ := cmd.Output()
fmt.Println(string(result)) // "error"

// Run with context (timeout)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd = exec.CommandContext(ctx, "sleep", "10")
err = cmd.Run() // killed after 5s
10

JSON 与编码

JSON 序列化与反序列化

encoding/json 在 Go 结构体和 JSON 之间转换。Marshal(结构体 → JSON),Unmarshal(JSON → 结构体)。结构体标签 (json:"name") 控制字段命名和可见性。omitempty 跳过零值/空值。json:"-" 完全排除字段。这是 Go 中处理 API 请求/响应的标准方式。

go
import "encoding/json"

type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Email    string `json:"email,omitempty"` // omit if empty
    Password string `json:"-"`               // never serialize
    Age      int    `json:"age,omitempty"`
}

// Struct to JSON (marshal)
user := User{ID: 1, Name: "Alice", Age: 30}
data, err := json.Marshal(user)
// {"id":1,"name":"Alice","age":30}

// Pretty print
pretty, _ := json.MarshalIndent(user, "", "  ")

// JSON to struct (unmarshal)
jsonStr := `{"id":2,"name":"Bob","email":"[email protected]"}`
var u User
err := json.Unmarshal([]byte(jsonStr), &u)
fmt.Println(u.Name) // "Bob"

// Struct tags control field names:
// json:"name"        -> rename
// json:"name,omitempty" -> skip if zero value
// json:"-"           -> skip entirely
// json:",omitempty"  -> keep name, skip if empty

使用 Map 和 Slice 的 JSON

对于动态 JSON(结构未知),反序列化到 map[string]any(或 interface{})。JSON 数字变为 float64 —— 需类型断言访问。json.Decoder/Encoder 高效处理流(文件、HTTP body)。已知 schema 时使用结构体反序列化;灵活/动态数据使用 map。

go
// Parse arbitrary JSON into map[string]interface{}
jsonStr := `{"name":"Alice","scores":[90,85,92],"active":true}`

var result map[string]any
json.Unmarshal([]byte(jsonStr), &result)

// Type-assert to access values
name := result["name"].(string)
scores := result["scores"].([]any)
for _, s := range scores {
    fmt.Println(s.(float64)) // JSON numbers are float64
}

// Encode a map
m := map[string]any{
    "count": 42,
    "items": []string{"a", "b", "c"},
    "meta":  map[string]any{"page": 1},
}
data, _ := json.Marshal(m)

// Decode JSON stream
dec := json.NewDecoder(strings.NewReader(jsonStr))
var v map[string]any
dec.Decode(&v)

// Encode to stream
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "  ")
enc.Encode(user)

自定义 JSON 序列化

实现 MarshalJSON/UnmarshalJSON 进行自定义序列化。适用于:计算字段、替代格式(Money 显示为 '99.99 USD')、敏感数据处理和时间格式化。UnmarshalJSON 的方法接收者必须是指针才能修改结构体。time.Time 自动序列化为 RFC 3339 字符串。

go
// Implement MarshalJSON/UnmarshalJSON
type Money struct {
    Amount   float64
    Currency string
}

func (m Money) MarshalJSON() ([]byte, error) {
    return json.Marshal(map[string]any{
        "amount":   m.Amount,
        "currency": m.Currency,
        "display":  fmt.Sprintf("%.2f %s", m.Amount, m.Currency),
    })
}

func (m *Money) UnmarshalJSON(data []byte) error {
    var v struct {
        Amount   float64
        Currency string
    }
    if err := json.Unmarshal(data, &v); err != nil {
        return err
    }
    m.Amount = v.Amount
    m.Currency = v.Currency
    return nil
}

money := Money{Amount: 99.99, Currency: "USD"}
data, _ := json.Marshal(money)
// {"amount":99.99,"currency":"USD","display":"99.99 USD"}

// Time formatting
type Event struct {
    Time time.Time `json:"time"`
}
// time.Time marshals as RFC 3339 by default

HTTP 服务器

net/http 包构建 HTTP 服务器。http.HandleFunc 注册处理程序。http.ResponseWriter 写入响应;*http.Request 读取请求。对于 JSON API,设置 Content-Type 并使用 json.NewEncoder(w).Encode(data)。标准库已达到生产就绪 —— 简单 API 无需框架。复杂路由使用 mux 路由器 (gorilla/mux, chi)。

go
import (
    "encoding/json"
    "net/http"
)

type Response struct {
    Status  string `json:"status"`
    Message string `json:"message"`
}

func main() {
    // Simple handler
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
    })

    // JSON API endpoint
    http.HandleFunc("/api/user", func(w http.ResponseWriter, r *http.Request) {
        user := User{ID: 1, Name: "Alice"}
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(user)
    })

    // Start server
    fmt.Println("Server running on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

// Request methods:
// r.Method  -> "GET", "POST", etc.
// r.URL.Query().Get("key") -> query params
// r.Body    -> request body (io.ReadCloser)

HTTP 客户端

http.Get/Post 是便捷快捷方式。对于自定义头、方法或 body,使用 http.NewRequest + client.Do()。始终 defer resp.Body.Close() 以避免连接泄漏。在客户端设置超时(默认无超时 —— 危险)。生产环境中复用 http.Client(它连接池化)并使用 context 进行取消。

go
// Simple GET
resp, err := http.Get("https://api.example.com/users")
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
fmt.Println(string(body))

// Custom request with headers
req, err := http.NewRequest("GET", "https://api.example.com/data", nil)
req.Header.Set("Authorization", "Bearer token123")
req.Header.Set("Accept", "application/json")

resp, err := http.DefaultClient.Do(req)
defer resp.Body.Close()

// POST with JSON body
user := User{Name: "Alice"}
body, _ := json.Marshal(user)
resp, err := http.Post(
    "https://api.example.com/users",
    "application/json",
    bytes.NewBuffer(body),
)
defer resp.Body.Close()

// With timeout
client := &http.Client{Timeout: 10 * time.Second}
resp, err = client.Get("https://slow-api.example.com")
11

测试与基准测试

单元测试

测试文件以 _test.go 结尾,测试函数以 Test 开头。表驱动测试是惯用模式:在切片中定义测试用例,用 t.Run 循环创建子测试(命名、可单独运行)。使用 t.Errorf 报告失败(继续),t.Fatalf 报告致命失败(停止)。用 'go test -v' 运行获取详细输出。

go
// math_test.go (file must end with _test.go)
package math

import "testing"

// Test function: func TestXxx(t *testing.T)
func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5
    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}

// Table-driven tests (idiomatic Go)
func TestAddTable(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 2, 3, 5},
        {"negative", -1, -1, -2},
        {"zero", 0, 0, 0},
        {"mixed", -5, 10, 5},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d",
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

// Run: go test -v
// Run specific: go test -run TestAddTable/positive

基准测试

基准测试函数以 Benchmark 开头并使用 *testing.B。b.N 循环由运行时调整以获得稳定测量。用 'go test -bench=.' 运行。b.ResetTimer() 排除设置时间。b.ReportAllocs() 显示内存分配。用 'benchstat' 比较实现以验证改进是否显著。

go
// Benchmark function: func BenchmarkXxx(b *testing.B)
func BenchmarkFibonacci(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Fibonacci(20)
    }
}

// Benchmark with allocations
func BenchmarkStringConcat(b *testing.B) {
    for i := 0; i < b.N; i++ {
        s := ""
        for j := 0; j < 100; j++ {
            s += "a"
        }
    }
}

// Run: go test -bench=.
// Output:
// BenchmarkFibonacci-8        300     4234567 ns/op
// BenchmarkStringConcat-8     200     6789012 ns/op   100 B/op   1 allocs/op

// Benchmark with setup
func BenchmarkProcess(b *testing.B) {
    data := setup() // setup not measured
    b.ResetTimer()  // start timing here
    for i := 0; i < b.N; i++ {
        process(data)
    }
}

// Memory allocations
func BenchmarkAllocs(b *testing.B) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        make([]int, 100)
    }
}

测试辅助与 Mock

t.Helper() 通过在堆栈跟踪中跳过辅助函数来改善错误消息。通过实现接口进行 mock(Go 的 mock 方式 —— 无需 mock 框架)。t.Cleanup() 注册清理函数(类似 defer,但用于测试作用域)。对于复杂 mock,使用 testify/assert 和 mockery 或 gomock 从接口生成 mock。

go
// t.Helper() marks helper functions (better error locations)
func assertEqual(t *testing.T, got, want int) {
    t.Helper()
    if got != want {
        t.Errorf("got %d, want %d", got, want)
    }
}

// Interfaces for mocking
type DataStore interface {
    Get(key string) (string, error)
}

type MockStore struct {
    data map[string]string
}

func (m *MockStore) Get(key string) (string, error) {
    if v, ok := m.data[key]; ok {
        return v, nil
    }
    return "", errors.New("not found")
}

func TestService(t *testing.T) {
    store := &MockStore{data: map[string]string{"key": "value"}}
    svc := NewService(store)
    result := svc.GetValue("key")
    assertEqual(t, result, "value")
}

// Subtests with t.Run for setup/teardown
func TestWithCleanup(t *testing.T) {
    t.Cleanup(func() {
        // runs after test (LIFO order)
        os.Remove("tempfile")
    })
    // test code
}

标准库要点

Go 标准库非常全面。time 处理日期/时间(注意:格式使用参考时间 2006-01-02)。regexp 用于模式匹配。context 用于跨 goroutine 的取消/超时。sync.Pool 用于对象复用(减少 GC 压力)。Go 1.21+ 添加了带泛型工具的 slices 和 maps 包。

go
// time package
now := time.Now()
future := now.Add(24 * time.Hour)
formatted := now.Format("2006-01-02 15:04:05")
parsed, _ := time.Parse("2006-01-02", "2024-01-15")

// regexp
re := regexp.MustCompile(`\d{4}-\d{2}-\d{2}`)
matches := re.FindString("date: 2024-01-15")
all := re.FindAllString("2024-01-15 and 2024-02-20", -1)

// context (cancellation, timeouts)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
go doWork(ctx)
// cancel() or timeout stops doWork

// sync.Pool (reuse objects)
var bufPool = sync.Pool{
    New: func() any { return new(bytes.Buffer) },
}
buf := bufPool.Get().(*bytes.Buffer)
defer bufPool.Put(buf)

// sort (Go 1.21+ slices package)
slices.Sort(nums)
slices.Contains(nums, 42)
slices.Reverse(nums)
12

Goroutine 深入

启动 Goroutine 与 WaitGroup

Goroutine 是由 Go 运行时管理的轻量级线程(非 OS 线程)—— 可以启动数十万个。sync.WaitGroup 协调 goroutine 完成:启动前 Add(1),完成时 Done()(使用 defer),Wait() 阻塞直到计数器归零。始终传递 WaitGroup 的指针,使所有 goroutine 共享同一计数器。

go
package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // signal completion when function returns
    fmt.Printf("Worker %d started\n", id)
    // ... do work ...
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1) // increment counter before starting goroutine
        go worker(i, &wg)
    }

    wg.Wait() // block until all goroutines call Done()
    fmt.Println("All workers finished")
}

GOMAXPROCS 与调度

GOMAXPROCS 控制同时运行 goroutine 的 OS 线程数 —— 默认为 CPU 核心数,几乎总是最优的。Go 使用 M:N 调度:少量 OS 线程上运行许多 goroutine。阻塞 I/O 或通道操作使调度器在同一线程上运行其他 goroutine。runtime.Gosched() 显式让出。很少需要调整 GOMAXPROCS。

go
package main

import (
    "fmt"
    "runtime"
)

func main() {
    // GOMAXPROCS: number of OS threads available to run goroutines
    fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) // defaults to CPU cores

    // Set it manually (rarely needed — default is optimal)
    runtime.GOMAXPROCS(2)

    // Goroutines are multiplexed onto OS threads (M:N scheduling)
    // - Blocking syscalls (I/O) don't block other goroutines
    // - The scheduler steals work across threads
    // - A goroutine blocked on channel/IO yields its thread

    // Number of goroutines currently running
    go func() { runtime.Gosched() }() // yield to other goroutines
    fmt.Println("Goroutines:", runtime.NumGoroutine())
}

Goroutine 泄漏与预防

当 goroutine 永久阻塞时(例如向无人读取的无缓冲通道发送)会发生 goroutine 泄漏 —— 它永远不会被垃圾回收。通过以下方式预防:缓冲通道、使用带取消通道/context 的 select,并始终提供退出路径。泄漏的 goroutine 累积内存和 CPU。使用 runtime.NumGoroutine() 和 pprof 在生产中检测泄漏。

go
package main

// BAD: goroutine leaks — this goroutine never exits
func leaky() <-chan int {
    ch := make(chan int)
    go func() {
        ch <- 42 // blocks forever if nobody reads from ch
    }()
    return ch
}

// GOOD: use context for cancellation
func safe(ctx context.Context) <-chan int {
    ch := make(chan int, 1) // buffered — sender never blocks
    go func() {
        select {
        case ch <- 42:
        case <-ctx.Done(): // exit when context is cancelled
            return
        }
    }()
    return ch
}

// GOOD: always ensure senders can exit
// 1. Buffer the channel so send doesn't block
// 2. Use select with a done/cancel channel
// 3. Use context.WithCancel/WithTimeout for lifecycle control

sync.Once(一次性初始化)

sync.Once 确保函数在所有 goroutine 中只执行一次 —— 实现线程安全单例和延迟初始化的标准方式。它比互斥锁保护的标志检查更高效。Do 方法阻塞并发调用者直到第一次调用完成。sync.Once 也用于许多标准库模式内部,如 sync.OnceValue (Go 1.21+)。

go
package main

import (
    "fmt"
    "sync"
)

var (
    instance *Database
    once     sync.Once
)

type Database struct{ name string }

func GetDB() *Database {
    // sync.Once guarantees the function runs exactly once,
    // even if called from many goroutines simultaneously
    once.Do(func() {
        instance = &Database{name: "production"}
        fmt.Println("Database initialized")
    })
    return instance
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            GetDB() // "Database initialized" prints only once
        }()
    }
    wg.Wait()
}

带 Goroutine 的工作池

工作池模式将并发限制为固定数量的 goroutine 处理来自通道的作业。这防止了无限制生成 goroutine 导致的资源耗尽。工作器使用 'for job := range jobs',在通道关闭时退出。close(jobs) 通知所有工作器停止。这是有界并行性的经典 Go 并发模式。

go
package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs { // exits when jobs channel is closed
        results <- job * job // process and send result
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    var wg sync.WaitGroup

    // Start 3 workers
    for w := 1; w <= 3; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }

    // Send 5 jobs
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs) // signal workers to stop (range loop exits)

    wg.Wait()
    close(results)

    for r := range results {
        fmt.Println("Result:", r)
    }
}
13

通道与 Select

通道基础(无缓冲与有缓冲)

无缓冲通道 (make(chan T)) 同步发送方和接收方 —— 发送阻塞直到接收方就绪。有缓冲通道 (make(chan T, n)) 允许无接收方时发送 n 次,解耦发送方/接收方时序。只有发送方应关闭通道(表示'不再有值')。从已关闭通道接收返回零值且 ok=false。range 循环在通道关闭时退出。

go
package main

import "fmt"

func main() {
    // Unbuffered: send blocks until someone receives (synchronous)
    ch := make(chan int)
    go func() {
        ch <- 42 // blocks until main reads
    }()
    fmt.Println(<-ch) // 42

    // Buffered: send blocks only when buffer is full
    buf := make(chan int, 3)
    buf <- 1 // doesn't block (buffer has space)
    buf <- 2
    buf <- 3
    // buf <- 4 // would block — buffer full
    fmt.Println(<-buf) // 1 (FIFO)

    // Close a channel (sender closes, never the receiver)
    close(buf)

    // Check if closed (ok is false when closed and empty)
    val, ok := <-buf
    fmt.Println(val, ok) // 2 true

    // Range over channel until closed
    nums := make(chan int, 3)
    nums <- 10; nums <- 20; nums <- 30
    close(nums)
    for n := range nums {
        fmt.Println(n) // 10, 20, 30
    }
}

Select 语句

select 让 goroutine 同时等待多个通道操作 —— 它选择第一个就绪的 case(多个就绪时随机选择)。default case 使 select 非阻塞。time.After 创建超时通道。select 是 Go 并发协调的核心:多路复用、超时、取消和扇入/扇出模式都基于它构建。

go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "from ch1"
    }()
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "from ch2"
    }()

    // select waits on multiple channel operations
    // picks the first one that's ready (random if multiple ready)
    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch1:
            fmt.Println(msg)
        case msg := <-ch2:
            fmt.Println(msg)
        }
    }

    // Non-blocking receive with default
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    default:
        fmt.Println("no message ready") // runs immediately
    }

    // Timeout with time.After
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    case <-time.After(500 * time.Millisecond):
        fmt.Println("timed out")
    }
}

扇入与扇出模式

扇出将工作分配给多个 goroutine 实现并行;扇入将多个通道合并为一个。它们共同形成并行管道:扇出工作器独立处理,扇入收集结果。扇入使用 WaitGroup 在所有输入通道耗尽后才关闭合并通道。这些模式是 Go 并发数据处理的基础。

go
package main

import (
    "fmt"
    "sync"
)

// Fan-Out: distribute work across multiple goroutines
func fanOut(input <-chan int, n int) []<-chan int {
    outputs := make([]<-chan int, n)
    for i := 0; i < n; i++ {
        outputs[i] = process(input) // each worker reads from same input
    }
    return outputs
}

func process(input <-chan int) <-chan int {
    output := make(chan int)
    go func() {
        defer close(output)
        for v := range input {
            output <- v * v // transform
        }
    }()
    return output
}

// Fan-In: merge multiple channels into one
func fanIn(channels ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    merged := make(chan int)

    output := func(c <-chan int) {
        defer wg.Done()
        for v := range c {
            merged <- v
        }
    }

    wg.Add(len(channels))
    for _, c := range channels {
        go output(c)
    }

    go func() {
        wg.Wait()
        close(merged)
    }()
    return merged
}

方向通道(只发送/只接收)

方向通道类型(<-chan T 只接收,chan<- T 只发送)在编译时强制通道使用方式。双向通道在传递给函数时隐式转换为方向类型。这记录意图并防止 bug —— 生产者函数字面上无法从自己的输出通道接收。在函数签名中使用方向类型使契约清晰。

go
package main

import "fmt"

// <-chan int: receive-only (can read, cannot send)
// chan<- int: send-only (can send, cannot read)
func producer(out chan<- int) { // can only send
    for i := 0; i < 3; i++ {
        out <- i
    }
    close(out)
}

func consumer(in <-chan int) { // can only receive
    for v := range in {
        fmt.Println("consumed:", v)
    }
}

func main() {
    ch := make(chan int)

    // Directional restrictions are enforced at compile time
    go producer(ch) // bidirectional chan converts to send-only
    consumer(ch)    // bidirectional chan converts to receive-only

    // This prevents bugs: producer can't accidentally read,
    // consumer can't accidentally send or close
}

Ticker 与 Timer 通道

time.Ticker 按间隔重复触发 —— 在 select 中使用 ticker.C 作为通道处理周期性任务。time.Timer 在持续时间后触发一次。始终在 ticker 和 timer 上调用 Stop() 以释放资源并避免泄漏。time.After 是便捷的一次性定时器,返回通道(但无法取消,因此在 select 循环中优先使用 NewTimer 以避免累积)。Reset 允许重新调度定时器。

go
package main

import (
    "fmt"
    "time"
)

func main() {
    // Ticker: fires repeatedly at intervals
    ticker := time.NewTicker(500 * time.Millisecond)
    defer ticker.Stop() // always stop to release resources

    go func() {
        for t := range ticker.C { // ticker.C is a channel
            fmt.Println("Tick at", t)
        }
    }()

    // Timer: fires once after a duration
    timer := time.NewTimer(2 * time.Second)
    <-timer.C // blocks until timer fires
    fmt.Println("Timer fired!")

    // time.After: one-shot timer as a channel (no Stop needed)
    select {
    case <-time.After(1 * time.Second):
        fmt.Println("1 second elapsed")
    }

    // Reset a timer (cancel and reschedule)
    timer2 := time.NewTimer(5 * time.Second)
    timer2.Reset(100 * time.Millisecond)
    <-timer2.C
}
14

Context 包

context.WithCancel 与 WithTimeout

context.Context 跨 goroutine 边界携带取消、超时和请求作用域值。WithCancel 返回 context 和取消函数;WithTimeout 在持续时间后自动取消。Goroutine 在 select 中检查 ctx.Done()(一个通道)以了解何时停止。始终调用取消函数(使用 defer)以释放资源,即使超时触发 —— 否则 context 泄漏。

go
package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context, id int) {
    for {
        select {
        case <-ctx.Done(): // cancelled or timed out
            fmt.Printf("Worker %d stopped: %v\n", id, ctx.Err())
            return
        default:
            fmt.Printf("Worker %d working...\n", id)
            time.Sleep(500 * time.Millisecond)
        }
    }
}

func main() {
    // WithCancel: manual cancellation
    ctx, cancel := context.WithCancel(context.Background())
    go worker(ctx, 1)
    time.Sleep(2 * time.Second)
    cancel() // stop the worker
    time.Sleep(500 * time.Millisecond)

    // WithTimeout: auto-cancel after duration
    ctx2, cancel2 := context.WithTimeout(context.Background(), 1500*time.Millisecond)
    defer cancel2() // always call cancel to release resources
    go worker(ctx2, 2)
    time.Sleep(2 * time.Second) // worker stops after 1.5s
}

通过调用传播 Context

Context 应是每个执行 I/O 的函数的第一个参数,并应通过整个调用链传播。http.Request.Context() 在客户端断开连接时自动取消。将 ctx 传递给数据库/HTTP 操作 (QueryRowContext, NewRequestWithContext) 确保它们在 context 取消时中止 —— 防止浪费工作和资源泄漏。切勿将 context 存储在结构体中。

go
package main

import (
    "context"
    "database/sql"
    "net/http"
)

// Context should be the FIRST parameter, named ctx
func GetUser(ctx context.Context, db *sql.DB, id int) (string, error) {
    // Pass ctx to all blocking operations so they cancel together
    var name string
    err := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id=?", id).Scan(&name)
    return name, err
}

func handler(w http.ResponseWriter, r *http.Request) {
    // r.Context() is cancelled when the client disconnects
    ctx := r.Context()

    name, err := GetUser(ctx, db, 42)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    w.Write([]byte(name))
}

// BEST PRACTICE: pass context through every function in the call chain
// that might do I/O. This ensures a client disconnect or timeout
// cancels ALL in-flight work (DB queries, HTTP calls, etc.)

Context 值(请求作用域数据)

context.WithValue 存储请求作用域数据(如用户 ID、追踪 ID、认证令牌),通过调用链流动。使用自定义键类型(而非字符串)以避免键冲突。值应是请求所需的数据,而非函数参数 —— Go 团队建议谨慎使用,主要用于追踪/认证等横切关注点。检索值时始终进行类型断言。

go
package main

import (
    "context"
    "fmt"
    "net/http"
)

// Define a custom key type to avoid collisions
type contextKey string

const userIDKey contextKey = "userID"

// Set a value in context
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        userID := 42 // authenticate...
        // WithValue creates a new context with the value
        ctx := context.WithValue(r.Context(), userIDKey, userID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// Get a value from context (must type-assert)
func getUserID(ctx context.Context) (int, bool) {
    if v, ok := ctx.Value(userIDKey).(int); ok {
        return v, true
    }
    return 0, false
}

func handler(w http.ResponseWriter, r *http.Request) {
    if uid, ok := getUserID(r.Context()); ok {
        fmt.Fprintf(w, "User ID: %d", uid)
    }
}

使用 Context 优雅关闭

优雅关闭让进行中的请求在服务器退出前完成。signal.Notify 捕获 OS 信号(Ctrl+C、来自容器编排器的 SIGTERM)。server.Shutdown(ctx) 停止接受新连接并等待活动连接完成(直到 context 超时)。这对生产服务器至关重要 —— 没有它,活动请求会被突然终止,导致错误和数据损坏。

go
package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    server := &http.Server{Addr: ":8080", Handler: mux}

    // Listen for OS interrupt signals (Ctrl+C, SIGTERM)
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        log.Println("Server starting on :8080")
        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Server error: %v", err)
        }
    }()

    <-stop // block until signal received
    log.Println("Shutting down...")

    // Give in-flight requests 10 seconds to finish
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Printf("Forced shutdown: %v", err)
    }
    log.Println("Server stopped gracefully")
}

Context 截止时间与错误处理

context.WithDeadline 在绝对时间取消;WithTimeout 在相对持续时间后取消(WithTimeout 就是 WithDeadline(now+timeout))。ctx.Err() 返回 context.DeadlineExceeded 或 context.Canceled,可区分停止原因。使用 errors.Is() 检查 context 错误。始终在长操作开始时检查 ctx.Err(),并在阻塞等待期间使用 select 配合 ctx.Done()。

go
package main

import (
    "context"
    "errors"
    "fmt"
    "time"
)

func slowOperation(ctx context.Context) (string, error) {
    // Check if already cancelled before starting
    if err := ctx.Err(); err != nil {
        return "", err
    }

    select {
    case <-time.After(3 * time.Second): // simulate slow work
        return "result", nil
    case <-ctx.Done(): // cancelled or deadline exceeded
        return "", ctx.Err()
    }
}

func main() {
    // Deadline: cancel at a specific time
    deadline := time.Now().Add(1 * time.Second)
    ctx, cancel := context.WithDeadline(context.Background(), deadline)
    defer cancel()

    result, err := slowOperation(ctx)
    if err != nil {
        if errors.Is(err, context.DeadlineExceeded) {
            fmt.Println("Operation timed out")
        } else if errors.Is(err, context.Canceled) {
            fmt.Println("Operation was cancelled")
        }
        return
    }
    fmt.Println(result)
}
15

HTTP 服务器与客户端

HTTP 服务器 (net/http)

net/http 提供生产就绪的 HTTP 服务器。http.HandleFunc 按路径注册处理程序。使用 json.NewEncoder(w).Encode() 写入 JSON 响应,json.NewDecoder(r.Body).Decode() 解析请求 body。始终设置 Content-Type 头并检查 r.Method。http.Error 发送错误状态。ListenAndServe 启动服务器;用 log.Fatal 包装以捕获错误。

go
package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func getUser(w http.ResponseWriter, r *http.Request) {
    user := User{ID: 1, Name: "Alice"}

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

func createUser(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    user.ID = 99
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(user)
}

func main() {
    http.HandleFunc("/users/1", getUser)
    http.HandleFunc("/users", createUser)

    log.Println("Server on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

HTTP 客户端与请求

默认 http.Client 没有超时 —— 始终设置一个以避免在无响应服务器上永久挂起。使用 http.NewRequestWithContext 附加 context 进行取消/超时。始终 defer resp.Body.Close() 以避免连接泄漏。生产环境中复用单个 http.Client(它管理连接池化)。http.Get 是快捷方式但缺少超时和自定义。

go
package main

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "time"
)

func main() {
    // Simple GET
    resp, err := http.Get("https://api.example.com/users")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close() // ALWAYS close the body

    var users []map[string]any
    json.NewDecoder(resp.Body).Decode(&users)
    fmt.Println(users)

    // Custom client with timeout (default has NO timeout!)
    client := &http.Client{Timeout: 30 * time.Second}

    // POST with JSON body and context
    body, _ := json.Marshal(map[string]string{"name": "Bob"})
    req, _ := http.NewRequestWithContext(context.Background(),
        "POST", "https://api.example.com/users", bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")

    resp, err = client.Do(req)
    defer resp.Body.Close()
    fmt.Println("Status:", resp.Status)
}

中间件模式

中间件包装处理程序以添加横切关注点(日志、认证、CORS、限流)而无需修改处理程序本身。签名 func(http.Handler) http.Handler 是标准中间件类型。Chain 按顺序应用(最外层先)。此模式是 Chi、Echo 和 Gin 等框架的基础。ResponseWriter 可被包装以捕获状态码用于日志。

go
package main

import (
    "log"
    "net/http"
    "time"
)

// Middleware wraps an http.Handler to add cross-cutting concerns
type Middleware func(http.Handler) http.Handler

func Logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r) // call the wrapped handler
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

func Auth(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Header.Get("Authorization") == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// Chain multiple middlewares
func Chain(h http.Handler, mws ...Middleware) http.Handler {
    for i := len(mws) - 1; i >= 0; i-- {
        h = mws[i](h)
    }
    return h
}

func main() {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello!"))
    })
    // Apply middlewares: Auth -> Logging -> handler
    http.Handle("/", Chain(handler, Logging, Auth))
    http.ListenAndServe(":8080", nil)
}

提供静态文件与模板

http.FileServer 从目录提供静态文件;http.StripPrefix 移除路径前缀使文件路径正确解析。html/template 安全渲染 HTML(自动转义以防止 XSS)。template.Must 在解析错误时 panic(启动时没问题)。模板使用 {{.Field}} 访问数据和 {{range}} 迭代。生产环境中考虑用 go:embed 嵌入文件而非从磁盘读取。

go
package main

import (
    "html/template"
    "net/http"
)

func main() {
    // Serve static files (CSS, JS, images)
    fs := http.FileServer(http.Dir("./static"))
    http.Handle("/static/", http.StripPrefix("/static/", fs))

    // HTML templates
    tmpl := template.Must(template.ParseFiles("templates/index.html"))

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        data := struct {
            Title string
            Items []string
        }{
            Title: "My Page",
            Items: []string{"Apple", "Banana", "Cherry"},
        }
        tmpl.Execute(w, data)
    })

    http.ListenAndServe(":8080", nil)
}

// templates/index.html:
// <h1>{{.Title}}</h1>
// <ul>{{range .Items}}<li>{{.}}</li>{{end}}</ul>

优雅关闭与 go:embed

go:embed 在构建时将文件打包到编译的二进制文件中 —— 实现真正的单二进制部署,无外部文件依赖。在 var 声明上方使用 //go:embed 指令。embed.FS 是只读虚拟文件系统。fs.Sub 创建子文件系统(去除目录前缀)。适用于静态资源、HTML 模板、SQL 迁移、配置文件等。

go
package main

import (
    "embed"
    "io/fs"
    "net/http"
)

//go:embed static/*
var staticFiles embed.FS

func main() {
    // Embed static files into the binary (no external files needed)
    // Access the embedded filesystem
    sub, _ := fs.Sub(staticFiles, "static")
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(sub))))

    // go:embed benefits:
    // - Single binary deployment (no external files)
    // - Files are compiled into the executable
    // - Works with templates, configs, migrations, etc.

    // Embed a single file
    //go:embed config.json
    // var configData []byte

    // Embed with pattern
    //go:embed templates/*.html
    // var templates embed.FS

    http.ListenAndServe(":8080", nil)
}
16

Sync 包

sync.Mutex 与 sync.RWMutex

sync.Mutex 提供独占锁定 —— 一次只有一个 goroutine 能持有它。sync.RWMutex 允许多个并发读取者或一个独占写入者 —— 在读取远多于写入时使用。始终将 Lock 与 defer Unlock 配对,以防止函数 panic 时死锁。将互斥锁嵌入结构体(小写 mu)以保持私有。切勿复制互斥锁(始终使用指针)。

go
package main

import (
    "fmt"
    "sync"
)

type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock() // always unlock with defer
    c.count++
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

// RWMutex: allows multiple readers OR one writer
type SafeCache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *SafeCache) Get(key string) (string, bool) {
    c.mu.RLock()         // read lock (multiple readers OK)
    defer c.mu.RUnlock()
    val, ok := c.data[key]
    return val, ok
}

func (c *SafeCache) Set(key, val string) {
    c.mu.Lock()          // write lock (exclusive)
    defer c.mu.Unlock()
    c.data[key] = val
}

sync.Map(并发 Map)

sync.Map 是并发安全的 map,针对特定场景优化:一次写入多次读取(缓存)或跨 goroutine 的不相交键访问。它通过原子操作避免读取锁。然而,对于通用并发 map,由 sync.RWMutex 保护的常规 map 通常更快更易用。sync.Map 的 API 使用 any (interface{}) 作为键和值,失去类型安全。

go
package main

import (
    "fmt"
    "sync"
)

func main() {
    var m sync.Map

    // Store and Load (thread-safe, no locks needed)
    m.Store("name", "Alice")
    m.Store("age", 30)

    val, ok := m.Load("name")
    fmt.Println(val, ok) // Alice true

    // LoadOrStore: atomic get-or-set
    actual, loaded := m.LoadOrStore("name", "Bob")
    fmt.Println(actual, loaded) // Alice true (already existed)

    // Delete
    m.Delete("age")

    // Range over all entries
    m.Range(func(key, value any) bool {
        fmt.Printf("%v = %v\n", key, value)
        return true // continue; return false to stop
    })

    // Use sync.Map when:
    // 1. Keys are written once, read many times (caches)
    // 2. Multiple goroutines read/write disjoint keys
    // For most cases, a regular map + Mutex is simpler and faster
}

sync.Cond(条件变量)

sync.Cond 实现条件变量 —— goroutine 等待条件变为真。Wait() 原子地释放锁并休眠;Signal() 唤醒一个等待者,Broadcast() 唤醒所有。始终在 Wait() 周围使用 for 循环(而非 if)以处理虚假唤醒。Cond 适用于生产者-消费者队列和等待状态变化,尽管通道通常提供更简单的替代方案。

go
package main

import (
    "fmt"
    "sync"
    "time"
)

type Queue struct {
    items []int
    mu    sync.Mutex
    cond  *sync.Cond
}

func NewQueue() *Queue {
    q := &Queue{}
    q.cond = sync.NewCond(&q.mu) // cond is tied to the mutex
    return q
}

func (q *Queue) Put(item int) {
    q.mu.Lock()
    defer q.mu.Unlock()
    q.items = append(q.items, item)
    q.cond.Signal() // wake one waiting goroutine
}

func (q *Queue) Get() int {
    q.mu.Lock()
    defer q.mu.Unlock()
    for len(q.items) == 0 {
        q.cond.Wait() // atomically unlocks mu, sleeps, re-locks on wake
    }
    item := q.items[0]
    q.items = q.items[1:]
    return item
}

func main() {
    q := NewQueue()
    go func() {
        time.Sleep(1 * time.Second)
        q.Put(42)
    }()
    fmt.Println("Waiting for item...")
    fmt.Println("Got:", q.Get()) // blocks until item available
}

sync.Pool(对象复用)

sync.Pool 复用对象以减少分配和 GC 压力 —— 非常适合频繁分配的短生命周期对象如 bytes.Buffer。Get() 返回池化对象(空时调用 New);Put() 归还。池在 GC 期间被清除,因此不要依赖它们持久化。复用前始终重置对象。标准库广泛使用 sync.Pool (http, json, fmt)。

go
package main

import (
    "bytes"
    "sync"
)

var bufPool = sync.Pool{
    New: func() any {
        return new(bytes.Buffer) // create when pool is empty
    },
}

func Process(data []byte) string {
    // Get a buffer from the pool (or create via New)
    buf := bufPool.Get().(*bytes.Buffer)
    defer bufPool.Put(buf) // return it when done

    buf.Reset() // clear before reuse
    buf.Write(data)
    buf.WriteString("-processed")
    return buf.String()
}

// Benefits:
// - Reduces GC pressure by reusing objects
// - Avoids allocation overhead for short-lived objects
// - Pool entries can be garbage collected between GC cycles
//
// Use for: bytes.Buffer, json encoders, temporary slices
// NOT for: long-lived objects or when size is unpredictable

errgroup(带错误传播的 Group)

errgroup(来自 golang.org/x/sync)是返回第一个错误并可通过 context 取消剩余 goroutine 的 WaitGroup。g.Go() 启动 goroutine;g.Wait() 阻塞并返回第一个非 nil 错误。WithContext 创建一个 context,当任何 goroutine 返回错误时被取消 —— 使其他 goroutine 提前停止。这是运行应全部成功或一起失败的并行操作的惯用方式。

go
package main

import (
    "context"
    "fmt"
    "golang.org/x/sync/errgroup"
    "net/http"
)

// errgroup is like WaitGroup but propagates the first error
// and can cancel remaining goroutines on failure
func fetchAll(urls []string) ([]*http.Response, error) {
    g, ctx := errgroup.WithContext(context.Background())
    results := make([]*http.Response, len(urls))

    for i, url := range urls {
        i, url := i, url // capture loop variables
        g.Go(func() error {
            req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
            resp, err := http.DefaultClient.Do(req)
            if err != nil {
                return err // cancels ctx, stopping other goroutines
            }
            results[i] = resp
            return nil
        })
    }

    if err := g.Wait(); err != nil {
        return nil, err // first error returned
    }
    return results, nil
}

func main() {
    urls := []string{
        "https://example.com",
        "https://golang.org",
    }
    resps, err := fetchAll(urls)
    fmt.Println(len(resps), err)
}
17

泛型 (Go 1.18+)

泛型函数与类型参数

Go 泛型 (1.18+) 使用方括号中的类型参数:[T any] 声明 T 为任意类型。编译器从参数推断类型,因此很少显式指定。泛型支持类型安全的可复用函数如 Map/Filter/Reduce,无需 interface{} 和类型断言。'any' 是随泛型引入的 interface{} 别名。

go
package main

import "fmt"

// Type parameters in brackets: [T any] means T can be any type
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func Filter[T any](slice []T, predicate func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
    result := initial
    for _, v := range slice {
        result = fn(result, v)
    }
    return result
}

func main() {
    nums := []int{1, 2, 3, 4, 5}
    squared := Map(nums, func(n int) int { return n * n })
    fmt.Println(squared) // [1 4 9 16 25]

    evens := Filter(nums, func(n int) bool { return n%2 == 0 })
    fmt.Println(evens) // [2 4]

    sum := Reduce(nums, 0, func(acc, n int) int { return acc + n })
    fmt.Println(sum) // 15

    // Works with any type
    words := []string{"go", "rust", "python"}
    lengths := Map(words, func(s string) int { return len(s) })
    fmt.Println(lengths) // [2 4 6]
}

类型约束与 comparable

类型约束限制泛型接受的类型。'comparable' 是支持 == 的内置约束(map 键和比较所需)。自定义约束使用类型联合 (int | float64) 允许特定类型。golang.org/x/exp/constraints 包提供 Ordered(用于 <、> 等)。约束可结合类型集和方法。

go
package main

import "fmt"

// comparable: types that support == and != (built-in constraint)
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

// Custom constraint with a type set
type Number interface {
    int | int64 | float64 | float32
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

// Multiple type parameters
func Pair[T, U any](first T, second U) struct{ First T; Second U } {
    return struct{ First T; Second U }{first, second}
}

func main() {
    fmt.Println(Contains([]string{"a", "b", "c"}, "b")) // true
    fmt.Println(Contains([]int{1, 2, 3}, 5))             // false

    fmt.Println(Sum([]int{1, 2, 3}))           // 6
    fmt.Println(Sum([]float64{1.5, 2.5}))      // 4

    p := Pair("Alice", 30)
    fmt.Println(p) // {Alice 30}
}

泛型数据结构

泛型类型 (Stack[T any]) 创建类型安全的数据结构,适用于任何类型而无需 interface{} 装箱或类型断言。类型参数 T 是类型的一部分,因此 Stack[int] 和 Stack[string] 是不同的、编译时检查的类型。这消除了整类运行时类型错误。泛型结构体、方法和接口都支持类型参数。

go
package main

import "fmt"

// Generic Stack — works with any type, type-safe
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    index := len(s.items) - 1
    item := s.items[index]
    s.items = s.items[:index]
    return item, true
}

func (s *Stack[T]) Len() int {
    return len(s.items)
}

func main() {
    // Type-safe stack of ints
    intStack := &Stack[int]{}
    intStack.Push(1)
    intStack.Push(2)
    val, _ := intStack.Pop()
    fmt.Println(val) // 2

    // Type-safe stack of strings
    strStack := &Stack[string]{}
    strStack.Push("hello")
    s, _ := strStack.Pop()
    fmt.Println(s) // hello

    // intStack.Push("oops") // compile error! type-safe
}

带 ~ 的泛型约束(底层类型)

约束中的 ~ 前缀匹配底层类型为命名类型的任何类型 —— 因此 ~string 同时匹配 string 和 type MyString string。没有 ~,约束只匹配确切的命名类型,这对自定义类型很少有用。当希望泛型适用于类型别名和从基本类型派生的命名类型时使用 ~(在领域建模中常见)。

go
package main

import "fmt"

// ~ allows any type whose UNDERLYING type matches
// (e.g., type MyString string would match ~string)
type StringLike interface {
    ~string
}

func Join[S StringLike](items []S, sep string) string {
    result := ""
    for i, item := range items {
        if i > 0 {
            result += sep
        }
        result += string(item) // convert underlying type to string
    }
    return result
}

// Numeric constraint with ~
type Integer interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

func Double[T Integer](v T) T {
    return v * 2
}

// Custom type based on int
type Score int

func main() {
    type MyString string
    items := []MyString{"a", "b", "c"}
    fmt.Println(Join(items, "-")) // a-b-c

    s := Double(Score(21))
    fmt.Println(s) // 42
}

泛型 vs 接口(何时使用)

当算法在各类型间相同(集合、数学、转换)时选择泛型 —— 它们提供编译时类型安全且零运行时开销。当不同类型需要不同实现(多态)或需要运行时分发(依赖注入、mock)时选择接口。它们互补:泛型可被接口约束 (T Stringer) 以获得两全其美。

go
package main

import "fmt"

// GENERICS: best for algorithms/data structures that work
// the same way regardless of type (collections, sorting, mapping)
func Max[T int | float64](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// INTERFACES: best when different types need DIFFERENT implementations
// (polymorphism, dependency injection, mocking)
type Stringer interface {
    String() string
}

type Dog struct{ Name string }
func (d Dog) String() string { return "Dog: " + d.Name }

type Cat struct{ Name string }
func (c Cat) String() string { return "Cat: " + c.Name }

func Print[T Stringer](v T) {
    fmt.Println(v.String())
}

func main() {
    fmt.Println(Max(3, 7))       // 7
    fmt.Println(Max(3.14, 2.71)) // 3.14

    Print(Dog{"Rex"}) // Dog: Rex
    Print(Cat{"Whiskers"}) // Cat: Whiskers
}

// RULE OF THUMB:
// - Same logic, different types -> Generics
// - Different logic, shared contract -> Interfaces
// - Need runtime polymorphism -> Interfaces
// - Need compile-time type safety -> Generics
18

反射

reflect.Type 与 reflect.Value

reflect.TypeOf 返回运行时类型;reflect.ValueOf 返回运行时值。从 Type 可检查结构体字段、方法和标签。从 Value 可读取和(在 CanSet 时)修改字段。反射强大但缓慢(比直接访问慢 10-100 倍)且绕过编译时类型安全 —— 谨慎使用,主要用于序列化、ORM 和框架。

go
package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    u := User{Name: "Alice", Age: 30}

    // Get type and value at runtime
    t := reflect.TypeOf(u)
    v := reflect.ValueOf(u)

    fmt.Println("Type:", t.Name())        // User
    fmt.Println("Kind:", t.Kind())         // struct
    fmt.Println("NumFields:", t.NumField())

    // Iterate struct fields
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := v.Field(i)
        tag := field.Tag.Get("json")
        fmt.Printf("%s (%s) = %v [json:%s]\n",
            field.Name, field.Type, value, tag)
    }
    // Name (string) = Alice [json:name]
    // Age (int) = 30 [json:age]
}

用反射修改值

要通过反射修改值,必须传递指针并调用 .Elem() 解引用。CanSet() 报告字段是否可赋值(可寻址值的导出字段)。未导出(小写)字段可读但不可设置。SetInt/SetString/Set 在运行时进行类型检查,不匹配时 panic。基于反射的修改是配置解析器和 和 ORM 的基础。

go
package main

import (
    "fmt"
    "reflect"
)

func main() {
    type Config struct {
        Port int
        Host string
    }

    cfg := Config{Port: 8080, Host: "localhost"}

    // MUST pass a pointer to modify (ValueOf of a struct is a copy)
    v := reflect.ValueOf(&cfg).Elem()

    // Check if a field is settable
    portField := v.FieldByName("Port")
    fmt.Println("Settable:", portField.CanSet()) // true

    // Modify fields by name
    portField.SetInt(9090)
    v.FieldByName("Host").SetString("0.0.0.0")

    fmt.Println(cfg) // {9090 0.0.0.0}

    // Set via interface (type-checked at runtime)
    v.FieldByName("Port").Set(reflect.ValueOf(7070))
    fmt.Println(cfg) // {7070 0.0.0.0}

    // Unexported fields are NOT settable (CanSet returns false)
}

动态调用方法

反射可按名称动态调用方法 —— 适用于 RPC 框架、插件系统和路由。MethodByName 返回 Value;Call() 用 reflect.Value 参数切片调用并返回结果切片。NumMethod/Method 枚举方法。通过反射的方法调用缓慢且绕过类型安全,因此仅在方法名在编译时未知时使用。

go
package main

import (
    "fmt"
    "reflect"
)

type Calculator struct{}

func (c Calculator) Add(a, b int) int { return a + b }
func (c Calculator) Mul(a, b int) int { return a * b }

func main() {
    calc := Calculator{}
    v := reflect.ValueOf(calc)

    // Find a method by name
    method := v.MethodByName("Add")
    fmt.Println("Found:", method.IsValid()) // true

    // Call with arguments (each arg must be a reflect.Value)
    args := []reflect.Value{
        reflect.ValueOf(3),
        reflect.ValueOf(4),
    }
    results := method.Call(args)
    fmt.Println("3 + 4 =", results[0].Int()) // 7

    // List all methods
    t := reflect.TypeOf(calc)
    for i := 0; i < t.NumMethod(); i++ {
        m := t.Method(i)
        fmt.Printf("Method: %s, Type: %v\n", m.Name, m.Type)
    }
    // Method: Add, Type: func(main.Calculator, int, int) int
    // Method: Mul, Type: func(main.Calculator, int, int) int
}

实际应用:结构体转 Map(基于标签)

此模式 —— 迭代结构体字段并读取标签 —— 是 encoding/json、YAML 解析器、ORM 和验证库底层的工作方式。reflect 使编写基于标签处理任何结构体的通用代码成为可能。json:'-' 标签约定(跳过此字段)是标准的。这是 Go 中最合法的反射用途之一。

go
package main

import (
    "fmt"
    "reflect"
)

// Convert any struct to a map using struct tags as keys
func StructToMap(obj any, tag string) map[string]any {
    result := make(map[string]any)
    v := reflect.ValueOf(obj)
    t := reflect.TypeOf(obj)

    // Dereference pointers
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
        t = t.Elem()
    }

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        // Use the tag value if present, else the field name
        key := field.Tag.Get(tag)
        if key == "" {
            key = field.Name
        }
        // Skip fields tagged with "-"
        if key == "-" {
            continue
        }
        result[key] = v.Field(i).Interface()
    }
    return result
}

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    Pass string `json:"-"` // excluded
}

func main() {
    u := User{Name: "Alice", Age: 30, Pass: "secret"}
    m := StructToMap(u, "json")
    fmt.Println(m) // map[age:30 name:Alice]
}
19

构建与工具

go build、go run 与 go install

go build 编译为可执行文件;go run 编译到临时文件并运行(非常适合开发);go install 将二进制文件放入 $GOPATH/bin 以全局访问。Go 的突出特性是通过 GOOS/GOARCH 轻松交叉编译 —— 无需工具链。-ldflags='-s -w' 去除调试信息使二进制文件减小约 30%。-X 在构建时注入值(如版本字符串)用于 CI/CD。

go
# Build an executable (outputs to current directory)
go build -o myapp              # custom output name
go build -o bin/myapp ./cmd    # build a specific package

# Build for different OS/arch (cross-compilation)
GOOS=linux GOARCH=amd64 go build -o myapp-linux
GOOS=windows GOARCH=amd64 go build -o myapp.exe
GOOS=darwin GOARCH=arm64 go build -o myapp-mac  # Apple Silicon

# Run without building an executable (compiles to temp, runs, deletes)
go run main.go                 # run single file
go run ./cmd/server            # run a package

# Install: builds and puts binary in $GOPATH/bin
go install ./cmd/myapp         # now 'myapp' is on your PATH

# Build flags
go build -v                    # verbose: print packages being compiled
go build -race                 # enable race detector (for testing)
go build -ldflags="-s -w"      # strip debug info (smaller binary)
go build -ldflags="-X main.Version=1.0.0"  # inject version at build time

go mod(模块管理)

go mod 通过 go.mod(依赖列表)和 go.sum(安全校验和)管理依赖。go get 添加/升级依赖;go mod tidy 同步文件(提交前运行)。Go 使用语义导入版本控制:v2+ 需要 /v2 路径后缀。模块缓存在 $GOPATH/pkg/mod 共享。go mod vendor 创建 vendor/ 目录用于可重现、离线或审计的构建。

go
# Initialize a new module
go mod init github.com/user/myproject

# This creates go.mod:
# module github.com/user/myproject
# go 1.21

# Add a dependency (auto-added to go.mod)
go get github.com/gin-gonic/gin@latest
go get github.com/lib/[email protected]  # specific version

# Tidy: add missing deps, remove unused ones
go mod tidy

# Download dependencies to local cache
go mod download

# Verify dependencies haven't been modified
go mod verify

# Vendor: copy deps into ./vendor (for offline builds)
go mod vendor

# Upgrade dependencies
go get -u github.com/lib/pq       # upgrade to latest minor
go get -u=patch github.com/lib/pq # upgrade patches only
go get github.com/lib/[email protected]  # pin specific version

# View dependency graph
go mod graph | head

go test 与基准测试

go test 运行 _test.go 文件;名为 TestXxx(t *testing.T) 的函数是测试,BenchmarkXxx(b *testing.B) 是基准测试。-race 启用数据竞争检测器(对并发代码至关重要)。-cover 显示测试覆盖率;-coverprofile 生成详细报告。基准测试运行 b.N 次迭代,N 自动调整。表驱动测试(输入/预期的切片)是惯用的 Go 测试风格。

go
# Run all tests in the current package
go test ./...

# Verbose output
go test -v ./...

# Run a specific test
go test -run TestAdd -v

# Run benchmarks
go test -bench=. -benchmem

# Race detector (find data races)
go test -race ./...

# Coverage
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out  # view in browser

# --- test file example (xxx_test.go) ---
# func TestAdd(t *testing.T) {
#     got := Add(2, 3)
#     if got != 5 {
#         t.Errorf("Add(2,3) = %d, want 5", got)
#     }
# }
#
# func BenchmarkAdd(b *testing.B) {
#     for i := 0; i < b.N; i++ {
#         Add(2, 3)
#     }
# }

go fmt、go vet 与 golangci-lint

go fmt/gofmt 强制单一规范格式 —— Go 没有格式化争论。go vet 捕获常见 bug(printf 不匹配、锁复制、错误结构体标签)。golangci-lint 聚合数十个 linter,是 CI 的行业标准。每次提交前运行 gofmt 和 go vet;将 golangci-lint 添加到 CI 进行更深入分析。一致的工具链是 Go 代码库看起来统一的主要原因。

go
# go fmt: format code (the ONE true Go style)
go fmt ./...              # format all files
gofmt -d main.go          # show diff without changing
gofmt -w main.go          # write changes in place

# go vet: static analysis for common mistakes
go vet ./...              # check all packages
# Catches: printf format mismatches, unreachable code,
# struct tag errors, lock copies, shadowed variables

# golangci-lint: meta-linter (runs many linters)
# Install: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
golangci-lint run         # run all enabled linters
golangci-lint run --enable=gosec,goconst  # enable specific linters

# Common linters:
# errcheck    - check unchecked errors
# gosec       - security issues
# govet       - go vet checks
# staticcheck - advanced static analysis
# ineffassign - detect ineffectual assignments
# unused      - find unused code

# goimports: fmt + auto-manage imports
# go install golang.org/x/tools/cmd/goimports@latest
goimports -w main.go

性能分析与 pprof

pprof 是 Go 的内置分析器 —— CPU、内存、goroutine 和互斥锁分析。对于长时间运行的服务器,导入 _ 'net/http/pprof' 暴露 /debug/pprof/ 端点进行实时分析而无需重启。go tool pprof 提供交互式 shell (top, list, web) 或 Web UI。执行追踪器 (go tool trace) 可视化 goroutine 调度和阻塞。性能分析对性能关键的 Go 代码至关重要。

go
# CPU profiling
go test -cpuprofile=cpu.prof -bench=.
go tool pprof cpu.prof
# Interactive commands: top, list FunctionName, web (graphviz)

# Memory profiling
go test -memprofile=mem.prof -bench=.
go tool pprof mem.prof

# Built-in HTTP pprof endpoint (for production servers)
import _ "net/http/pprof"
# Then visit: http://localhost:8080/debug/pprof/
# Capture a 30-second CPU profile:
# go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30

# In code:
import "runtime/pprof"
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// ... code to profile ...

# Trace (execution tracer)
go test -trace=trace.out -bench=.
go tool trace trace.out  # opens in browser
20

数据库 (SQL)

database/sql 基础

database/sql 是 SQL 数据库的标准接口。驱动因副作用被导入(注册自身)。sql.Open 不建立连接;使用 Ping 验证。始终 defer db.Close()。

go
import "database/sql"
import _ "github.com/lib/pq"
db, err := sql.Open("postgres", "host=localhost dbname=mydb")
if err != nil { log.Fatal(err) }
defer db.Close()
err = db.Ping()

查询多行

Query 返回多行。始终 defer rows.Close() 释放资源。Scan 将列值复制到变量。循环后检查 rows.Err() 获取迭代错误。

go
rows, err := db.Query("SELECT id, name FROM users WHERE age > $1", 18)
if err != nil { log.Fatal(err) }
defer rows.Close()
for rows.Next() {
    var id int; var name string
    if err := rows.Scan(&id, &name); err != nil { log.Fatal(err) }
    fmt.Printf("%d: %s\n", id, name)
}

查询单行

QueryRow 返回单行。若无匹配行,Scan 返回 sql.ErrNoRows。比 Query 更适合单行查找。始终显式处理 ErrNoRows。

go
var name string
err := db.QueryRow("SELECT name FROM users WHERE id = $1", 1).Scan(&name)
if err != nil {
    if err == sql.ErrNoRows { fmt.Println("Not found") } else { log.Fatal(err) }
}

预处理语句

Prepare 创建可复用语句,提高重复查询性能。防止 SQL 注入。始终 defer stmt.Close()。用于多次执行的查询。

go
stmt, err := db.Prepare("INSERT INTO users(name) VALUES($1)")
if err != nil { log.Fatal(err) }
defer stmt.Close()
_, err = stmt.Exec("Alice")
_, err = stmt.Exec("Bob")

事务

Begin 启动事务。tx 内所有操作都是原子的。defer Rollback 是安全的:Commit 后它是 no-op。如果任何操作失败,Rollback 撤销所有更改。

go
tx, err := db.Begin()
if err != nil { log.Fatal(err) }
defer tx.Rollback()  // Safe to call after commit
_, err = tx.Exec("UPDATE accounts SET bal = bal - 100 WHERE id = 1")
_, err = tx.Exec("UPDATE accounts SET bal = bal + 100 WHERE id = 2")
if err != nil { log.Fatal(err) }
err = tx.Commit()
21

测试深入

基本测试

测试函数以 Test 开头并接受 *testing.T。t.Errorf 记录失败并继续。t.Fatalf 记录并停止。用 go test 运行。使用 testify/assert 获得更清晰的断言。

go
func TestAdd(t *testing.T) {
    got := Add(2, 3)
    want := 5
    if got != want {
        t.Errorf("Add(2,3) = %d; want %d", got, want)
    }
}

表驱动测试

表驱动测试是 Go 的惯用法。将测试用例定义为结构体切片。循环遍历并运行每个。t.Run 创建子测试以单独定位失败。

go
func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, want int
    }{
        {1, 2, 3}, {-1, 1, 0}, {0, 0, 0},
    }
    for _, tt := range tests {
        got := Add(tt.a, tt.b)
        if got != tt.want {
            t.Errorf("Add(%d,%d)=%d; want %d", tt.a, tt.b, got, tt.want)
        }
    }
}

子测试

子测试使用带名称和函数的 t.Run。用 -run 模式运行特定子测试。提供更好的测试组织和输出。

go
func TestAdd(t *testing.T) {
    t.Run("positive", func(t *testing.T) {
        if Add(1, 2) != 3 { t.Error("failed") }
    })
    t.Run("negative", func(t *testing.T) {
        if Add(-1, -2) != -3 { t.Error("failed") }
    })
}
// Run specific: go test -run TestAdd/negative

TestMain

TestMain 为包运行一次,替换默认测试运行器。用于全局设置/拆卸。必须调用 m.Run() 执行测试。os.Exit 传播退出码。

go
func TestMain(m *testing.M) {
    setup()
    code := m.Run()
    teardown()
    os.Exit(code)
}
func setup() { /* initialize DB, etc. */ }
func teardown() { /* cleanup */ }

Mock 接口

Go 的 mock 依赖接口。定义接口,实现 mock,并注入。mockery 和 mockgen 等工具自动生成 mock。无需外部依赖即可进行单元测试。

go
type Store interface { Get(id int) (string, error) }
type MockStore struct { data map[int]string }
func (m *MockStore) Get(id int) (string, error) {
    if v, ok := m.data[id]; ok { return v, nil }
    return "", errors.New("not found")
}
// Use in tests by injecting MockStore
22

基准测试

基本基准测试

基准测试函数以 Benchmark 开头并接受 *testing.B。b.N 由框架调整以获得稳定测量。用 go test -bench 运行。输出显示 ns/op(每次操作纳秒)。

go
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}
// Run: go test -bench=.
// Output: BenchmarkAdd-8    1000000000    0.3 ns/op

子基准测试

子基准测试使用 b.Run。ResetTimer 排除设置时间。ReportAllocs 显示内存分配。比较不同输入大小的性能。

go
func BenchmarkSort(b *testing.B) {
    sizes := []int{100, 1000, 10000}
    for _, size := range sizes {
        b.Run(fmt.Sprintf("size-%d", size), func(b *testing.B) {
            data := generateData(size)
            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                Sort(data)
            }
        })
    }
}

内存分配

ReportAllocs 显示每次操作的内存分配。减少分配是 Go 性能的关键。使用 sync.Pool、预分配切片,避免不必要的字符串拼接。

go
func BenchmarkConcat(b *testing.B) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        s := "a" + "b" + "c"
        _ = s
    }
}
// Output includes allocs/op and B/op

并行基准测试

RunParallel 在多个 goroutine 间并发运行基准测试。适用于测试线程安全代码。pb.Next() 迭代工作分配。测量并发下的吞吐量。

go
func BenchmarkParallel(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            Add(2, 3)
        }
    })
}

基准测试比较

benchstat 统计比较基准测试结果。用 -count=10 多次运行基准测试以获得可靠比较。帮助验证性能改进和检测回归。

go
# Run with memory stats
benchstat old.txt new.txt
# Compare two runs
# name      old time/op  new time/op  delta
# Add-8     2.3ns        1.8ns        -21.7%
23

性能分析 (pprof)

CPU 分析

StartCPUProfile 将 CPU profile 写入文件。StopCPUProfile 刷新数据。用 go tool pprof 分析。在 pprof 中使用 top、list、web 命令。关注消耗最多 CPU 的函数。

go
import "runtime/pprof"
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// Run your code here
// Analyze: go tool pprof cpu.prof

内存分析

WriteHeapProfile 捕获当前堆状态。先调用 runtime.GC() 获得准确结果。设置 MemProfileRate = 1 分析每次分配(较慢但精确)。

go
// Add to code
runtime.GC()
f, _ := os.Create("mem.prof")
pprof.WriteHeapProfile(f)
f.Close()
// Or use runtime.MemProfileRate = 1 for all allocations

HTTP 分析

net/http/pprof 在默认 mux 上注册分析端点。通过 HTTP 访问 profile 而无需重启。适用于生产诊断。在生产中保护端点安全。

go
import _ "net/http/pprof"
go func() {
    log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// Analyze live:
// go tool pprof http://localhost:6060/debug/pprof/profile

pprof 命令

top 显示消耗最多资源的函数。list 显示带注释的源代码。web 打开 SVG 调用图。tree 显示调用层次。使用 focus 过滤。

go
# Start interactive pprof
go tool pprof cpu.prof
(pprof) top 10        # Top functions by CPU
(pprof) list Add      # Show source with timings
(pprof) web           # Open graph in browser
(pprof) tree          # Call tree
(pprof) png > out.png # Save graph as PNG

Trace

runtime/trace 捕获执行追踪:goroutine 调度、GC、系统调用阻塞。go tool trace 打开 Web UI。适用于诊断延迟和并发问题。

go
import "runtime/trace"
f, _ := os.Create("trace.out")
trace.Start(f)
defer trace.Stop()
// Run code
// View: go tool trace trace.out
24

构建标签

基本构建标签

构建标签条件编译文件。//go:build 语法 (Go 1.17+) 替代 // +build。标签可以是 OS (linux, darwin, windows)、架构 (amd64, arm64) 或自定义。

go
//go:build linux
// +build linux

package main
// This file only compiles on Linux

多个标签

&&(旧语法中的逗号)要求所有标签。||(旧语法中的空格)要求任一标签。! 取反。用于平台特定实现。

go
//go:build linux && amd64
// +build linux,amd64

//go:build linux || darwin
// +build linux darwin

//go:build !windows
// +build !windows

自定义标签

自定义标签启用可选功能。用 -tags 标志构建。常见用途:调试构建、实验功能、企业版 vs 社区版。保持标签名小写。

go
//go:build debug
// +build debug

package main
const DebugMode = true
// Build with: go build -tags debug

文件后缀

文件后缀提供隐式构建约束。_linux、_darwin、_windows 用于 OS。_amd64、_arm64 用于架构。比标签更简单用于平台特定代码。

go
// file: utils_linux.go    -> Linux only
// file: utils_darwin.go   -> macOS only
// file: utils_windows.go  -> Windows only
// file: utils_amd64.go    -> amd64 only
// file: utils_debug.go    -> requires -tags debug

带标签构建

使用 -tags 启用条件编译。多个标签以空格分隔。Go 版本标签 (go1.18) 启用版本特定代码。用 go list -tags 验证。

go
# Build with debug tag
go build -tags debug
# Multiple tags
go build -tags "debug verbose"
# List available tags
go list -tags ./...
# Conditional in code
//go:build go1.18  // Requires Go 1.18+
25

CGO 基础

基本 CGO

CGO 支持从 Go 调用 C。C 代码在 import "C" 上方的注释中。import 必须紧跟在注释之后。CGO 减慢构建并阻止交叉编译。

go
/*
#include <stdio.h>
void hello() {
    printf("Hello from C!\n");
}
*/
import "C"
func main() {
    C.hello()
}

传递字符串

C.CString 分配 C 字符串(必须用 C.free 释放)。CString 复制数据。使用 unsafe.Pointer 转换。始终释放 C 分配的内存以避免泄漏。

go
/*
#include <string.h>
int len(const char* s) { return strlen(s); }
*/
import "C"
import "unsafe"
s := "Hello"cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
length := C.len(cs)

调用 C 库

#cgo LDFLAGS 链接 C 库。#cgo CFLAGS 设置编译器标志。用 #include 包含系统头文件。CGO 桥接 Go 和现有 C 库如 libm、libcrypto。

go
/*
#cgo LDFLAGS: -lm
#include <math.h>
*/
import "C"
result := float64(C.sqrt(16.0))
fmt.Println(result)  // 4

C 到 Go 回调

用 //export 导出的 Go 函数可从 C 调用。函数必须在 package main 中。使 C 库能回调到 Go。用于 FFI 绑定。

go
/*
extern void goCallback(int);
void callGo(int n) { goCallback(n); }
*/
import "C"
//export goCallback
func goCallback(n C.int) {
    fmt.Printf("Called with %d\n", n)
}

性能说明

与 Go 函数调用相比,CGO 调用有显著开销。避免在性能关键代码中使用。批量操作以减少跨边界调用。设置 CGO_ENABLED=0 进行纯 Go 构建。

go
// CGO calls have overhead (~100ns)
// Avoid in hot paths
// Batch C calls to amortize cost
// CGO disables inlining and some optimizations
// Build: CGO_ENABLED=1 go build
26

Web 框架

net/http 服务器

net/http 是标准库 HTTP 服务器。HandleFunc 注册处理程序。ListenAndServe 启动服务器。默认 mux 适用于简单应用;生产环境使用自定义 mux。

go
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
})
http.HandleFunc("/api", apiHandler)
log.Fatal(http.ListenAndServe(":8080", nil))

Gin 框架

Gin 是高性能 HTTP 框架。Param 提取 URL 参数。JSON 序列化响应。Gin 提供路由、中间件和 JSON 验证。由于 httprouter 比 net/http 更快。

go
import "github.com/gin-gonic/gin"
r := gin.Default()
r.GET("/users/:id", func(c *gin.Context) {
    id := c.Param("id")
    c.JSON(200, gin.H{"user": id})
})
r.Run(":8080")

中间件

中间件包装处理程序以添加横切关注点:日志、认证、CORS。c.Next() 调用下一个处理程序。gin.Recovery() 防止 panic 导致崩溃。顺序很重要。

go
func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next()
        fmt.Printf("%s %s %v\n", c.Request.Method, c.URL.Path, time.Since(start))
    }
}
r := gin.New()
r.Use(Logger(), gin.Recovery())

Echo 框架

Echo 是另一个流行框架,类似 Gin。处理程序返回错误用于集中错误处理。内置 CORS、JWT、限流中间件。简洁的 API 设计。

go
import "github.com/labstack/echo/v4"
e := echo.New()
e.GET("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    return c.JSON(200, map[string]string{"user": id})
})
e.Start(":8080")

静态文件

FileServer 提供静态文件。StripPrefix 调整 URL 路径。适用于提供 HTML、CSS、JS 和图片。生产环境使用 CDN 或 nginx 提供静态资源。

go
// net/http
fs := http.FileServer(http.Dir("./static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
// Gin
r.Static("/assets", "./assets")
r.StaticFile("/favicon.ico", "./favicon.ico")
27

gRPC

定义 Proto

Protocol Buffers 定义服务契约。proto3 是最新语法。service 定义 RPC 方法。message 定义数据结构。用 protoc 生成 Go 代码。

go
syntax = "proto3";
package greet;
service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest { string name = 1; }
message HelloReply { string message = 1; }

生成代码

protoc 从 .proto 文件生成 Go 代码。--go_out 生成消息类型。--go-grpc_out 生成服务存根。生成的代码不手动编辑。

go
# Install protoc and plugins
protoc --go_out=. --go-grpc_out=. \
    greet.proto
# This generates greet.pb.go and greet_grpc.pb.go
# Containing message types and service interfaces

服务器实现

嵌入 UnimplementedGreeterServer 以实现前向兼容。实现服务方法。grpc.NewServer 创建服务器。在服务前注册服务。

go
type server struct { greet.UnimplementedGreeterServer }
func (s *server) SayHello(ctx context.Context, in *greet.HelloRequest) (*greet.HelloReply, error) {
    return &greet.HelloReply{Message: "Hello " + in.Name}, nil
}
lis, _ := net.Listen("tcp", ":50051")
grpc.NewServer().Serve(lis)

客户端

grpc.Dial 建立连接。WithInsecure 禁用 TLS(生产环境使用 credentials.NewTLS)。客户端存根提供类型化方法。连接被池化和复用。

go
conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
client := greet.NewGreeterClient(conn)
resp, _ := client.SayHello(context.Background(), &greet.HelloRequest{Name: "Alice"})
fmt.Println(resp.Message)

流式传输

gRPC 支持三种流模式。stream 关键字标记流式传输。服务器流:一个请求,多个响应。双向:双方都流。适用于实时数据。

go
// Server streaming
rpc LotsOfReplies(HelloRequest) returns (stream HelloReply);
// Client streaming
rpc LotsOfGreetings(stream HelloRequest) returns (HelloReply);
// Bidirectional
rpc BidiHello(stream HelloRequest) returns (stream HelloReply);
28

错误包装

包装错误

使用 %w 动词包装错误,保留原始错误。这创建错误链。避免用 %v 包装,因为它丢失链。包装添加上下文而不丢失根本原因。

go
if err != nil {
    return fmt.Errorf("failed to open config: %w", err)
}

解包

errors.Is 检查链中是否有错误匹配。errors.As 从链中提取特定错误类型。哨兵值用 Is,类型化错误用 As。两者都遍历包装链。

go
err := someOperation()
if errors.Is(err, sql.ErrNoRows) {
    // Handle not found
}
var pathErr *fs.PathError
if errors.As(err, &pathErr) {
    fmt.Println("Path:", pathErr.Path)
}

自定义错误

自定义错误类型实现 error 接口。它们携带结构化数据用于错误处理。使用 errors.As 提取自定义类型。优先使用类型化错误而非字符串比较。

go
type ValidationError struct {
    Field string
    Msg   string
}
func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}
func validate(s string) error {
    if s == "" { return &ValidationError{Field: "name", Msg: "required"} }
    return nil
}

哨兵错误

哨兵错误是包级错误变量。用于预期错误条件。用 errors.Is 检查,切勿用 ==。导出它们供用户检查。

go
var ErrNotFound = errors.New("not found")
func Find(id int) (*User, error) {
    if id > 100 { return nil, ErrNotFound }
    return &User{}, nil
}
// Check with errors.Is
if errors.Is(err, ErrNotFound) { /* ... */ }

错误处理模式

尽可能立即处理错误。对于 Close 等延迟操作,捕获错误。命名返回值允许延迟函数修改返回值。始终检查 Close 错误。

go
// Immediate handling
if err := doSomething(); err != nil {
    return fmt.Errorf("operation failed: %w", err)
}
// Deferred error checking (e.g., Close)
func readFile() (err error) {
    f, e := os.Open("file.txt")
    if e != nil { return e }
    defer func() {
        if cerr := f.Close(); err == nil { err = cerr }
    }()
    // ...
}
29

常见陷阱

Goroutine 泄漏

当 goroutine 永久阻塞时会泄漏。始终提供退出路径:context 取消、关闭通道或有缓冲通道。使用 runtime.NumGoroutine() 检测泄漏。

go
// BUG: goroutine leaks if receiver stops
func send(ch chan int) {
    go func() { ch <- 1 }()  // Blocks forever
}
// FIX: use context or buffered channel
func send(ctx context.Context, ch chan int) {
    go func() {
        select {
        case ch <- 1:
        case <-ctx.Done():
        }
    }()
}

通道关闭

只有发送方应关闭通道,接收方不应。关闭表示不再有值。从已关闭通道接收返回零值。向已关闭通道发送会 panic。

go
// Only the sender should close a channel
// Closing from receiver causes panic
ch := make(chan int)
go func() {
    defer close(ch)  // Sender closes
    for i := 0; i < 5; i++ { ch <- i }
}()
for v := range ch { fmt.Println(v) }

循环变量捕获

Go 1.22 之前,循环变量在迭代间共享。捕获它们的 goroutine 看到最终值。Go 1.22+ 通过每次迭代创建新变量修复了此问题。旧版本中作为参数传递。

go
// BUG (Go < 1.22): all goroutines see last value
for i := 0; i < 3; i++ {
    go func() { fmt.Println(i) }()  // Prints 3,3,3
}
// FIX: pass as parameter
for i := 0; i < 3; i++ {
    go func(i int) { fmt.Println(i) }(i)
}

Map 并发

map 不是并发安全的。并发读写会导致运行时 panic。使用 sync.Mutex 进行显式锁定或 sync.Map 用于读密集型并发访问。

go
// BUG: concurrent map writes panic
m := map[int]int{}
go func() { m[1] = 1 }()
go func() { m[2] = 2 }()
// FIX: use sync.Map or mutex
var mu sync.Mutex
mu.Lock(); m[1] = 1; mu.Unlock()

Nil 接口

包装在接口中的 nil 指针不是 nil。即使值为 nil,接口也有类型。始终直接返回 nil,而非 nil 类型化指针。用 reflect 检查或显式返回 nil。

go
// BUG: nil check fails
type MyError struct{}
func (e *MyError) Error() string { return "err" }
func doSomething() error {
    var err *MyError = nil
    return err  // Non-nil interface!
}
// FIX: return nil explicitly
func doSomething() error {
    return nil
}

这篇内容对您有帮助吗?

学习路径

从零开始学习

通过结构化课程从头学习这个语言。