Go 学习笔记(七):进阶特性

写在前面

本文是 Go 学习笔记系列的第七篇,介绍 Go 的进阶特性:泛型、反射、逃逸分析和性能分析工具。前置知识:标准库与工程实践(第六篇)。


一、泛型(Go 1.18+)

1.1 泛型函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 泛型函数:支持多种类型
func Print[T any](value T) {
    fmt.Println(value)
}

Print(42)          // int
Print("hello")     // string
Print(3.14)        // float64

// 带约束的泛型
func Sum[T int | float64](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

Sum([]int{1, 2, 3})        // 6
Sum([]float64{1.1, 2.2})   // 3.3

1.2 类型约束

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 自定义类型约束
type Number interface {
    int | int8 | int16 | int32 | int64 |
    float32 | float64
}

func Max[T Number](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// comparable 约束(可比较的类型,支持 == 和 !=)
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

1.3 泛型切片工具

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 通用的 Map 函数(对切片每个元素做变换)
func Map[T any, 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
}

// 通用的 Filter 函数
func Filter[T any](slice []T, fn func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}

// 使用
nums := []int{1, 2, 3, 4, 5}
doubled := Map(nums, func(n int) int { return n * 2 })       // [2 4 6 8 10]
evens := Filter(nums, func(n int) bool { return n%2 == 0 })  // [2 4]

1.4 泛型结构体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 泛型栈
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
    }
    top := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return top, true
}

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

// 使用
stack := Stack[int]{}
stack.Push(1)
stack.Push(2)
v, _ := stack.Pop()   // 2

二、反射(reflect)

反射用于在运行时检查类型信息,主要场景:JSON 序列化、ORM、通用框架。

2.1 基本用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import "reflect"

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

user := User{Name: "张三", Age: 25}

// 获取类型信息
t := reflect.TypeOf(user)
fmt.Println(t.Name())    // User
fmt.Println(t.Kind())    // struct

// 获取值信息
v := reflect.ValueOf(user)
fmt.Println(v.Kind())    // struct

// 遍历结构体字段
for i := 0; i < t.NumField(); i++ {
    field := t.Field(i)
    value := v.Field(i)
    fmt.Printf("%s (%s) = %v, tag=%s\n",
        field.Name, field.Type, value.Interface(), field.Tag.Get("json"))
}
// Name (string) = 张三, tag=name
// Age (int) = 25, tag=age

2.2 修改值

1
2
3
4
5
6
7
8
// 必须传指针才能修改
user := User{Name: "张三"}
v := reflect.ValueOf(&user).Elem()

// 修改字段
nameField := v.FieldByName("Name")
nameField.SetString("李四")
fmt.Println(user.Name)   // 李四

2.3 调用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
type Calculator struct{}

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

c := Calculator{}
v := reflect.ValueOf(c)
method := v.MethodByName("Add")

args := []reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)}
results := method.Call(args)
fmt.Println(results[0].Int())   // 3

反射的代价:反射比直接调用慢很多,可读性也差。能用泛型替代的就不要用反射。


三、闭包进阶

3.1 闭包捕获变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func counter(start int) func() int {
    count := start
    return func() int {
        count++
        return count
    }
}

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

// 独立的闭包,互不影响
c2 := counter(100)
fmt.Println(c2())  // 101

3.2 闭包的常见用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 中间件模式
func withLogging(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        fn(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    }
}

// 延迟计算
func lazyCompute(fn func() int) func() int {
    var result int
    var computed bool
    return func() int {
        if !computed {
            result = fn()
            computed = true
        }
        return result
    }
}

四、内存与逃逸分析

4.1 栈和堆

1
2
栈 — 分配释放快,函数结束时自动回收
堆 — 需要 GC 回收,分配和回收都有开销

Go 编译器会自动决定变量分配在栈还是堆(逃逸分析):

4.2 逃逸到堆的情况

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 1. 返回局部变量的指针
func newUser() *User {
    u := User{Name: "张三"}
    return &u   // u 逃逸到堆
}

// 2. 发送到 channel
func sendToChannel(ch chan *User) {
    u := User{Name: "张三"}
    ch <- &u   // u 逃逸到堆
}

// 3. 在闭包中引用
func closure() func() int {
    x := 0
    return func() int {
        x++      // x 逃逸到堆
        return x
    }
}

// 4. 接口类型
func print(v interface{}) {
    fmt.Println(v)  // v 可能逃逸
}

// 5. 切片扩容
s := make([]int, 0)
for i := 0; i < 10000; i++ {
    s = append(s, i)  // 可能多次扩容,逃逸到堆
}

4.3 查看逃逸分析

1
2
3
4
5
6
7
8
9
# 编译时查看逃逸分析
go build -gcflags="-m" main.go

# 更详细的输出
go build -gcflags="-m -m" main.go

# 输出示例:
# ./main.go:5:6: &u escapes to heap
# ./main.go:5:6: moved to heap: u

4.4 减少逃逸的技巧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 不好的写法:返回指针
func createUser() *User {
    return &User{Name: "张三"}
}

// 好的写法:返回值类型
func createUser() User {
    return User{Name: "张三"}
}

// 不好的写法:频繁使用 interface{}
func process(v interface{}) { }

// 好的写法:使用泛型(Go 1.18+)
func process[T any](v T) { }

// 预分配切片容量,避免扩容
s := make([]int, 0, 1000)   // 一步到位

五、性能分析(pprof)

5.1 CPU 分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import "runtime/pprof"

func main() {
    // 创建 CPU profile 文件
    f, err := os.Create("cpu.prof")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    // 开始 CPU profiling
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()

    // 你的业务代码
    doWork()
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 分析 CPU profile
go tool pprof cpu.prof

# 常用 pprof 交互命令:
# top       — 显示最耗 CPU 的函数
# top 20    — 显示前20个
# list func — 显示某函数的代码和耗时
# web       — 生成可视化图表(需要安装 graphviz)

# 或者用 Web UI 查看
go tool pprof -http=:8080 cpu.prof

5.2 内存分析

1
2
3
4
5
6
7
8
9
func main() {
    doWork()

    // 写入内存 profile
    f, _ := os.Create("mem.prof")
    defer f.Close()
    runtime.GC()               // 先触发 GC
    pprof.WriteHeapProfile(f)
}
1
2
3
# 分析内存 profile
go tool pprof mem.prof
go tool pprof -http=:8080 mem.prof

5.3 HTTP 服务中集成 pprof

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import _ "net/http/pprof"

func main() {
    // 只需 import pprof,然后启动 HTTP 服务
    // 访问 http://localhost:8080/debug/pprof/ 即可
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    // 你的业务代码...
}
1
2
3
4
# 远程分析运行中的服务
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30   # CPU
go tool pprof http://localhost:6060/debug/pprof/heap                 # 内存
go tool pprof http://localhost:6060/debug/pprof/goroutine            # goroutine

5.4 benchmark 生成 profile

1
2
3
4
5
6
7
8
# 生成 CPU profile
go test -bench=. -cpuprofile=cpu.prof

# 生成内存 profile
go test -bench=. -memprofile=mem.prof

# 分析
go tool pprof cpu.prof

六、代码优化技巧

6.1 减少内存分配

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 使用 sync.Pool 复用对象
var bufPool = sync.Pool{
    New: func() any {
        return new(bytes.Buffer)
    },
}

func process() {
    buf := bufPool.Get().(*bytes.Buffer)
    defer func() {
        buf.Reset()
        bufPool.Put(buf)
    }()

    buf.WriteString("hello")
    // 使用 buf...
}

// 用 strings.Builder 拼接字符串(比 + 高效)
var builder strings.Builder
for _, s := range parts {
    builder.WriteString(s)
}
result := builder.String()

6.2 预分配容量

1
2
3
4
5
// 切片预分配
users := make([]User, 0, len(ids))

// Map 预分配
m := make(map[string]int, len(keys))

6.3 避免 []byte 和 string 频繁转换

1
2
3
// 频繁转换会产生内存分配
// 如果只是读,可以用 unsafe 零拷贝(高级用法,谨慎使用)
// 一般情况下用 bytes 包操作 []byte,减少转换

七、小结

本文学习了 Go 的进阶特性:

  • 泛型(类型参数、约束、泛型函数和结构体)
  • 反射(获取类型信息、修改值、调用方法)
  • 闭包进阶用法
  • 逃逸分析(理解栈和堆的分配)
  • 性能分析(pprof 的 CPU、内存分析)
  • 代码优化技巧

到此 Go 学习笔记的语法和工具部分已经覆盖完毕。下一篇将进入实战项目,把前面学到的知识综合运用。