go-data-structures
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo Data Structures
Go语言数据结构
Source: Effective Go
This skill covers Go's built-in data structures and allocation primitives.
来源:Effective Go
本技能涵盖Go语言的内置数据结构和内存分配原语。
Allocation: new vs make
内存分配:new 与 make
Go has two allocation primitives: and . They do different things.
newmakeGo语言有两个内存分配原语: 和 ,它们的功能各不相同。
newmakenew
new
new(T)T*Tgo
p := new(SyncedBuffer) // type *SyncedBuffer, zeroed
var v SyncedBuffer // type SyncedBuffer, zeroedZero-value design: Design data structures so the zero value is useful without
further initialization. Examples: , .
bytes.Buffersync.Mutexgo
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
// Ready to use immediately upon allocationnew(T)T*Tgo
p := new(SyncedBuffer) // 类型为*SyncedBuffer,已初始化为零值
var v SyncedBuffer // 类型为SyncedBuffer,已初始化为零值零值设计原则:设计数据结构时,应确保其零值无需进一步初始化即可直接使用。例如:、。
bytes.Buffersync.Mutexgo
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
// 分配后即可直接使用make
make
make(T, args)T*Tgo
make([]int, 10, 100) // slice: length 10, capacity 100
make(map[string]int) // map: ready to use
make(chan int) // channel: ready to usemake(T, args)T*Tgo
make([]int, 10, 100) // 切片:长度10,容量100
make(map[string]int) // 映射:可直接使用
make(chan int) // 通道:可直接使用The Difference
两者的区别
go
var p *[]int = new([]int) // *p == nil; rarely useful
var v []int = make([]int, 100) // v is a usable slice of 100 ints
// Idiomatic:
v := make([]int, 100)Rule: applies only to maps, slices, and channels and does not return
a pointer.
makego
var p *[]int = new([]int) // *p == nil;几乎无实用价值
var v []int = make([]int, 100) // v是可直接使用的、包含100个int的切片
// 惯用写法:
v := make([]int, 100)规则:仅适用于映射、切片和通道,且不返回指针。
makeComposite Literals
复合字面量
Create and initialize structs, arrays, slices, and maps in one expression:
go
// Struct with positional fields
f := File{fd, name, nil, 0}
// Struct with named fields (order doesn't matter, missing = zero)
f := &File{fd: fd, name: name}
// Zero value
f := &File{} // equivalent to new(File)
// Arrays, slices, maps
a := [...]string{Enone: "no error", Eio: "Eio", Einval: "invalid"}
s := []string{Enone: "no error", Eio: "Eio", Einval: "invalid"}
m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid"}Note: It's safe to return the address of a local variable in Go—the storage
survives after the function returns.
可通过单个表达式创建并初始化结构体、数组、切片和映射:
go
// 使用位置参数初始化结构体
f := File{fd, name, nil, 0}
// 使用命名参数初始化结构体(参数顺序无关,未指定的字段为零值)
f := &File{fd: fd, name: name}
// 零值初始化
f := &File{} // 等价于new(File)
// 数组、切片、映射的初始化
a := [...]string{Enone: "no error", Eio: "Eio", Einval: "invalid"}
s := []string{Enone: "no error", Eio: "Eio", Einval: "invalid"}
m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid"}注意:在Go语言中,返回局部变量的地址是安全的——该变量的存储空间在函数返回后依然保留。
Arrays
数组
Arrays are values in Go (unlike C):
- Assigning one array to another copies all elements
- Passing an array to a function passes a copy, not a pointer
- The size is part of the type: and
[10]intare distinct[20]int
go
func Sum(a *[3]float64) (sum float64) {
for _, v := range *a {
sum += v
}
return
}
array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array) // Pass pointer for efficiencyRecommendation: Use slices instead of arrays in most cases.
在Go语言中,数组是值类型(与C语言不同):
- 将一个数组赋值给另一个数组时,会复制所有元素
- 将数组传递给函数时,传递的是副本而非指针
- 数组大小是其类型的一部分:和
[10]int是不同的类型[20]int
go
func Sum(a *[3]float64) (sum float64) {
for _, v := range *a {
sum += v
}
return
}
array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array) // 传递指针以提升效率建议:大多数情况下优先使用切片而非数组。
Slices
切片
Slices wrap arrays to provide a flexible, powerful interface to sequences.
切片是对数组的封装,为序列数据提供灵活、强大的操作接口。
Slice Basics
切片基础
Slices hold references to an underlying array. Assigning one slice to another
makes both refer to the same array:
go
func (f *File) Read(buf []byte) (n int, err error)
// Read into first 32 bytes of larger buffer
n, err := f.Read(buf[0:32])切片持有对底层数组的引用。将一个切片赋值给另一个切片时,两者会指向同一个底层数组:
go
func (f *File) Read(buf []byte) (n int, err error)
// 将数据读取到更大缓冲区的前32个字节中
n, err := f.Read(buf[0:32])Length and Capacity
长度与容量
- : current length
len(s) - : maximum length (from start of slice to end of underlying array)
cap(s)
- :切片的当前长度
len(s) - :切片的最大长度(从切片起始位置到底层数组末尾的长度)
cap(s)
The append Function
append函数
go
func append(slice []T, elements ...T) []TAlways assign the result—the underlying array may change:
go
x := []int{1, 2, 3}
x = append(x, 4, 5, 6)
// Append a slice to a slice
y := []int{4, 5, 6}
x = append(x, y...) // Note the ...go
func append(slice []T, elements ...T) []T务必接收返回值——因为底层数组可能会发生变化:
go
x := []int{1, 2, 3}
x = append(x, 4, 5, 6)
// 将一个切片追加到另一个切片
y := []int{4, 5, 6}
x = append(x, y...) // 注意末尾的...Two-Dimensional Slices
二维切片
Method 1: Independent inner slices (can grow/shrink independently):
go
picture := make([][]uint8, YSize)
for i := range picture {
picture[i] = make([]uint8, XSize)
}Method 2: Single allocation (more efficient for fixed sizes):
go
picture := make([][]uint8, YSize)
pixels := make([]uint8, XSize*YSize)
for i := range picture {
picture[i], pixels = pixels[:XSize], pixels[XSize:]
}For detailed slice internals, see references/SLICES.md.
方法1:独立的内层切片(可各自独立增长/缩小):
go
picture := make([][]uint8, YSize)
for i := range picture {
picture[i] = make([]uint8, XSize)
}方法2:单次内存分配(对于固定大小的场景更高效):
go
picture := make([][]uint8, YSize)
pixels := make([]uint8, XSize*YSize)
for i := range picture {
picture[i], pixels = pixels[:XSize], pixels[XSize:]
}如需了解切片的详细内部实现,请参阅references/SLICES.md。
Declaring Empty Slices
声明空切片
Normative: This is required per Go Wiki CodeReviewComments.
When declaring an empty slice, prefer:
go
var t []stringover:
go
t := []string{}The former declares a nil slice, while the latter is non-nil but zero-length.
They are functionally equivalent—their and are both zero—but the nil
slice is the preferred style.
lencapException for JSON encoding: A nil slice encodes to , while an empty
slice encodes to . Use non-nil when you need a JSON array:
null[]string{}[]go
// nil slice → JSON null
var tags []string
json.Marshal(tags) // "null"
// empty slice → JSON array
tags := []string{}
json.Marshal(tags) // "[]"Interface design: When designing interfaces, avoid making a distinction
between a nil slice and a non-nil zero-length slice, as this can lead to subtle
programming errors.
规范要求:根据Go Wiki的CodeReviewComments,这是必须遵循的规范。
声明空切片时,优先使用:
go
var t []string而非:
go
t := []string{}前者声明的是nil切片,后者是非nil但长度为0的切片。两者在功能上等价——它们的和均为0,但nil切片是更推荐的写法。
lencapJSON编码例外:nil切片会被编码为,而空切片会被编码为。当你需要输出JSON数组时,请使用非nil的空切片:
null[]string{}[]go
// nil切片 → JSON null
var tags []string
json.Marshal(tags) // "null"
// 空切片 → JSON数组
tags := []string{}
json.Marshal(tags) // "[]"接口设计:设计接口时,应避免区分nil切片和非nil的零长度切片,否则可能导致微妙的编程错误。
Maps
映射
Maps associate keys with values. Keys must support equality ().
==映射用于关联键和值。键必须支持相等运算符()。
==Creating and Using Maps
创建与使用映射
go
var timeZone = map[string]int{
"UTC": 0*60*60,
"EST": -5*60*60,
"CST": -6*60*60,
}
offset := timeZone["EST"] // -18000go
var timeZone = map[string]int{
"UTC": 0*60*60,
"EST": -5*60*60,
"CST": -6*60*60,
}
offset := timeZone["EST"] // -18000Testing for Presence
检查键是否存在
An absent key returns the zero value. Use the "comma ok" idiom to distinguish:
go
seconds, ok := timeZone[tz]
if !ok {
log.Println("unknown time zone:", tz)
}
// Or combined:
if seconds, ok := timeZone[tz]; ok {
return seconds
}不存在的键会返回对应类型的零值。使用"逗号ok"惯用法来区分键不存在和键值为零值的情况:
go
seconds, ok := timeZone[tz]
if !ok {
log.Println("unknown time zone:", tz)
}
// 或合并写法:
if seconds, ok := timeZone[tz]; ok {
return seconds
}Deleting Entries
删除映射条目
go
delete(timeZone, "PDT") // Safe even if key doesn't existgo
delete(timeZone, "PDT") // 即使键不存在,该操作也是安全的Implementing a Set
实现集合
Use :
map[T]boolgo
attended := map[string]bool{"Ann": true, "Joe": true}
if attended[person] { // false if not in map
fmt.Println(person, "was at the meeting")
}使用来实现集合:
map[T]boolgo
attended := map[string]bool{"Ann": true, "Joe": true}
if attended[person] { // 若不在映射中则返回false
fmt.Println(person, "was at the meeting")
}Printing
格式化输出
The package provides rich formatted printing.
fmtfmtBasic Functions
基础函数
| Function | Output |
|---|---|
| Formatted to stdout |
| Returns formatted string |
| Formatted to io.Writer |
| Default format |
go
fmt.Printf("Hello %d\n", 23)
fmt.Println("Hello", 23)
s := fmt.Sprintf("Hello %d", 23)| 函数 | 输出目标 |
|---|---|
| 格式化输出到标准输出 |
| 返回格式化后的字符串 |
| 格式化输出到io.Writer |
| 使用默认格式输出 |
go
fmt.Printf("Hello %d\n", 23)
fmt.Println("Hello", 23)
s := fmt.Sprintf("Hello %d", 23)The %v Format
%v 格式符
%vgo
fmt.Printf("%v\n", timeZone)
// map[CST:-21600 EST:-18000 MST:-25200 PST:-28800 UTC:0]For structs:
- : values only
%v - : with field names
%+v - : full Go syntax
%#v
go
type T struct {
a int
b float64
c string
}
t := &T{7, -2.35, "abc\tdef"}
fmt.Printf("%v\n", t) // &{7 -2.35 abc def}
fmt.Printf("%+v\n", t) // &{a:7 b:-2.35 c:abc def}
fmt.Printf("%#v\n", t) // &main.T{a:7, b:-2.35, c:"abc\tdef"}%vgo
fmt.Printf("%v\n", timeZone)
// map[CST:-21600 EST:-18000 MST:-25200 PST:-28800 UTC:0]对于结构体:
- :仅打印字段值
%v - :打印字段名和对应值
%+v - :打印完整的Go语言语法表示
%#v
go
type T struct {
a int
b float64
c string
}
t := &T{7, -2.35, "abc\tdef"}
fmt.Printf("%v\n", t) // &{7 -2.35 abc def}
fmt.Printf("%+v\n", t) // &{a:7 b:-2.35 c:abc def}
fmt.Printf("%#v\n", t) // &main.T{a:7, b:-2.35, c:"abc\tdef"}Other Useful Formats
其他实用格式符
| Format | Purpose |
|---|---|
| Type of value |
| Quoted string |
| Hex (strings, bytes, ints) |
| 格式符 | 用途 |
|---|---|
| 打印值的类型 |
| 打印带引号的字符串 |
| 以十六进制打印(字符串、字节、整数) |
The Stringer Interface
Stringer接口
Define to control default formatting:
String() stringgo
func (t *T) String() string {
return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c)
}Warning: Don't call with on the receiver—infinite recursion:
Sprintf%sgo
// Bad: infinite recursion
func (m MyString) String() string {
return fmt.Sprintf("MyString=%s", m)
}
// Good: convert to basic type
func (m MyString) String() string {
return fmt.Sprintf("MyString=%s", string(m))
}通过定义方法,可以控制类型的默认格式化输出:
String() stringgo
func (t *T) String() string {
return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c)
}警告:不要在方法中使用格式化当前接收器,否则会导致无限递归:
%sgo
// 错误写法:无限递归
func (m MyString) String() string {
return fmt.Sprintf("MyString=%s", m)
}
// 正确写法:转换为基础类型
func (m MyString) String() string {
return fmt.Sprintf("MyString=%s", string(m))
}Constants and iota
常量与iota
Constants are created at compile time and can only be numbers, characters,
strings, or booleans.
常量在编译时创建,只能是数字、字符、字符串或布尔值。
iota Enumerator
iota 枚举器
iotago
type ByteSize float64
const (
_ = iota // ignore first value (0)
KB ByteSize = 1 << (10 * iota)
MB
GB
TB
PB
EB
)Combine with for automatic formatting:
String()go
func (b ByteSize) String() string {
switch {
case b >= EB:
return fmt.Sprintf("%.2fEB", b/EB)
case b >= PB:
return fmt.Sprintf("%.2fPB", b/PB)
// ... etc
}
return fmt.Sprintf("%.2fB", b)
}iotago
type ByteSize float64
const (
_ = iota // 忽略第一个值(0)
KB ByteSize = 1 << (10 * iota)
MB
GB
TB
PB
EB
)结合方法实现自动格式化:
String()go
func (b ByteSize) String() string {
switch {
case b >= EB:
return fmt.Sprintf("%.2fEB", b/EB)
case b >= PB:
return fmt.Sprintf("%.2fPB", b/PB)
// ... 其他情况
}
return fmt.Sprintf("%.2fB", b)
}Copying
复制注意事项
Advisory: This is a best practice recommendation from Go Wiki CodeReviewComments.
To avoid unexpected aliasing, be careful when copying a struct from another
package. For example, contains a slice. If you copy a
, the slice in the copy may alias the array in the original, causing
subsequent method calls to have surprising effects.
bytes.Buffer[]byteBuffergo
// Dangerous: copying a bytes.Buffer
var buf1 bytes.Buffer
buf1.WriteString("hello")
buf2 := buf1 // buf2's internal slice may alias buf1's array!
buf2.WriteString(" world") // May affect buf1 unexpectedlyGeneral rule: Do not copy a value of type if its methods are associated
with the pointer type .
T*TThis applies to many types in the standard library and third-party packages:
bytes.Buffer- ,
sync.Mutex,sync.WaitGroupsync.Cond - Types containing the above
go
// Bad: copying a mutex
var mu sync.Mutex
mu2 := mu // Copying a mutex is almost always a bug
// Good: use pointers or embed carefully
type SafeCounter struct {
mu sync.Mutex
count int
}
// Pass by pointer, not by value
func increment(sc *SafeCounter) {
sc.mu.Lock()
sc.count++
sc.mu.Unlock()
}最佳实践建议:来自Go Wiki的CodeReviewComments。
为避免意外的别名问题,当复制来自其他包的结构体时需格外小心。例如,包含一个切片。如果复制实例,副本中的切片可能会与原实例的底层数组产生别名,导致后续方法调用出现意外结果。
bytes.Buffer[]byteBuffergo
// 危险操作:复制bytes.Buffer
var buf1 bytes.Buffer
buf1.WriteString("hello")
buf2 := buf1 // buf2的内部切片可能与buf1的底层数组产生别名!
buf2.WriteString(" world") // 可能会意外影响buf1的内容通用规则:如果类型的方法是关联到指针类型的,不要复制类型的值;需警惕别名问题。
T*TT标准库和第三方包中的许多类型都符合这一情况:
bytes.Buffer- 、
sync.Mutex、sync.WaitGroupsync.Cond - 包含上述类型的自定义类型
go
// 错误写法:复制互斥锁
var mu sync.Mutex
mu2 := mu // 复制互斥锁几乎总是一个错误
// 正确写法:使用指针或谨慎嵌入
type SafeCounter struct {
mu sync.Mutex
count int
}
// 按指针传递,而非值传递
func increment(sc *SafeCounter) {
sc.mu.Lock()
sc.count++
sc.mu.Unlock()
}Quick Reference
快速参考
| Topic | Key Point |
|---|---|
| Returns |
| Slices, maps, channels only; returns |
| Arrays | Values, not references; size is part of type |
| Slices | Reference underlying array; use |
| Maps | Key must support |
| Copying | Don't copy |
| Default format for any value |
| Struct with field names |
| Full Go syntax |
| Enumerated constants |
| 主题 | 核心要点 |
|---|---|
| 返回 |
| 仅适用于切片、映射、通道;返回 |
| 数组 | 值类型,非引用类型;大小是类型的一部分 |
| 切片 | 引用底层数组;使用 |
| 映射 | 键必须支持 |
| 复制 | 若方法关联到 |
| 任意值的默认格式化符 |
| 打印带字段名的结构体 |
| 打印完整的Go语言语法表示 |
| 用于定义枚举常量 |
See Also
相关链接
- go-style-core - Core Go style principles
- go-control-flow - Control structures including range
- go-interfaces - Interface patterns and embedding
- go-concurrency - Channels and goroutines
- go-style-core - Go语言核心编码风格原则
- go-control-flow - 控制结构(包括range循环)
- go-interfaces - 接口模式与嵌入
- go-concurrency - 通道与goroutine