关于 go-data-structures
介绍 Go 数据结构选择以及切片、映射和复制值的实践建议,帮助开发者确定数据表示方式并避免常见的切片与别名问题。
主要功能
- 01数据结构选择建议
- 02切片声明与 append 实践
- 03二维切片模式
- 04基于映射的集合表示
- 05复制与别名风险提示
适用场景
- 01为 Go 功能选择集合类型
- 02处理 JSON 中 nil 与空切片
- 03避免复制值时产生别名问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ cxuu/ golang-skills --skill go-data-structures安装后在 Claude Code 中输入/go-data-structures确认已加载。
附带文件references/SLICES.md
详细介绍
Go 数据结构
资源路由
references/SLICES.md- 在决定使用 nil 切片还是空切片、复制切片,或管理切片容量和别名时阅读。
选择数据结构
What do you need?
├─ Ordered collection of items
│ ├─ Fixed size known at compile time → Array [N]T
│ └─ Dynamic size → Slice []T
│ ├─ Know approximate size? → make([]T, 0, capacity)
│ └─ Unknown size or nil-safe for JSON? → var s []T (nil)
├─ Key-value lookup
│ └─ Map map[K]V
│ ├─ Know approximate size? → make(map[K]V, capacity)
│ └─ Need a set? → map[T]struct{} (zero-size values)
└─ Need to pass to a function?
└─ Copy at the boundary if the caller might mutate it
此技能不适用的情况:对于数据结构的并发访问(互斥锁、原子操作),请参阅 go-concurrency。对于 API 边界处的防御性复制,请参阅 go-defensive。对于性能优化所需的容量预分配,请参阅 go-performance。
切片
append 函数
始终赋值其结果——底层数组可能会改变:
x := []int{1, 2, 3}
x = append(x, 4, 5, 6)
// Append a slice to a slice
x = append(x, y...) // Note the ...
二维切片
内层切片相互独立(可以独立增长或缩小):
picture := make([][]uint8, YSize)
for i := range picture {
picture[i] = make([]uint8, XSize)
}
单次分配(固定大小时效率更高):
picture := make([][]uint8, YSize)
pixels := make([]uint8, XSize*YSize)
for i := range picture {
picture[i], pixels = pixels[:XSize], pixels[XSize:]
}
声明空切片
优先使用 nil 切片,而不是空字面量:
// Good: nil slice
var t []string
// Avoid: non-nil but zero-length
t := []string{}
两者的 len 和 cap 都为零,但 nil 切片是更推荐的写法。
JSON 例外情况:nil 切片会编码为 null,而 []string{}
会编码为 []。需要 JSON 数组时,请使用非 nil 切片。
设计接口时,避免区分 nil 和非 nil 的零长度切片。
映射
实现集合
如果映射仅用于表示集合,请使用 map[T]struct{}。空结构体不占用存储空间,也能明确表达成员关系:
attended := map[string]struct{}{"Ann": {}, "Joe": {}}
if _, ok := attended[person]; ok {
fmt.Println(person, "was at the meeting")
}
仅当值除了表示成员是否存在之外还有其他含义时,才使用布尔型映射值。
复制
复制其他包中的结构体时要小心。如果该类型的方法定义在其指针类型(*T)上,复制该值可能导致别名问题。
**一般规则:**如果类型 T 的方法定义在指针类型 *T 上,就不要复制 T 类型的值。这适用于 bytes.Buffer、sync.Mutex、sync.WaitGroup 以及包含这些类型的类型。
// Bad: copying a mutex
var mu sync.Mutex
mu2 := mu // almost always a bug
// Good: pass by pointer
func increment(sc *SafeCounter) {
sc.mu.Lock()
sc.count++
sc.mu.Unlock()
}
快速参考
| 主题 | 要点 |
|---|---|
| 切片 | 始终赋值 append 的结果;优先使用 nil 切片,而不是 []T{} |
| 集合 | 仅用于表示成员关系的集合使用 map[T]struct{} |
| 复制 | 如果方法定义在 *T 上,就不要复制 T;注意别名问题 |
相关技能
- 防御性复制:在 API 边界处复制切片或映射以防止修改时,请参阅 go-defensive
- 容量提示:为已知工作负载预分配切片或映射容量时,请参阅 go-performance
- 迭代模式:使用 range 循环遍历切片、映射或通道时,请参阅 go-control-flow
- 声明风格:在
new、make、var和复合字面量之间进行选择时,请参阅 go-declarations
评论
支持 Markdown