写在前面
本文是 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 学习笔记的语法和工具部分已经覆盖完毕。下一篇将进入实战项目,把前面学到的知识综合运用。