mcpField
G
go-data-structures

go-data-structures

github.com/cxuu开发1861.2k 次安装19 周前更新
代码仓库

关于 go-data-structures

介绍 Go 数据结构选择以及切片、映射和复制值的实践建议,帮助开发者确定数据表示方式并避免常见的切片与别名问题。

主要功能

  1. 01数据结构选择建议
  2. 02切片声明与 append 实践
  3. 03二维切片模式
  4. 04基于映射的集合表示
  5. 05复制与别名风险提示

适用场景

  1. 01为 Go 功能选择集合类型
  2. 02处理 JSON 中 nil 与空切片
  3. 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