slice 和 map 包含了指向底层数据的指针,因此在需要复制它们时要特别注意。
在 Go 源码文件 src/runtime/slice.go 我们可以找到切片的底层数据结构 runtime.slice:
type slice struct {
array unsafe.Pointer
len int
cap int
}
在 Go 源码文件 src/runtime/map.go 我们可以找到 map 底层核心数据结构 runtime.hmap:
// A header for a Go map.
type hmap struct {
// Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
// Make sure this stays in sync with the compiler's definition.
count int // # live cells == size of map. Must be first (used by len() builtin)
flags uint8
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
hash0 uint32 // hash seed
buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
extra *mapextra // optional fields
}
// mapextra holds fields that are not present on all maps.
type mapextra struct {
// If both key and elem do not contain pointers and are inline, then we mark bucket
// type as containing no pointers. This avoids scanning such maps.
// However, bmap.overflow is a pointer. In order to keep overflow buckets
// alive, we store pointers to all overflow buckets in hmap.extra.overflow and hmap.extra.oldoverflow.
// overflow and oldoverflow are only used if key and elem do not contain pointers.
// overflow contains overflow buckets for hmap.buckets.
// oldoverflow contains overflow buckets for hmap.oldbuckets.
// The indirection allows to store a pointer to the slice in hiter.
overflow *[]*bmap
oldoverflow *[]*bmap
// nextOverflow holds a pointer to a free overflow bucket.
nextOverflow *bmap
}
在接收 slice 和 map 时,请记住当 map 或 slice 作为函数参数传入时,如果您存储了对它们的引用,则用户可以对其进行修改。
// Bas
func (d *Driver) SetTrips(trips []Trip) {
d.trips = trips
}
trips := ...
d.SetTrips(trips)
// 你是要修改 d.trips 吗?
trips[0] = ...
// Good
func (d *Driver) SetTrips(trips []Trip) {
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)
}
trips := ...
d.SetTrips(trips)
// 这里我们修改 trips[0],但不会影响 d.trips
trips[0] = ...
在返回 slice 和 map 时,同样的,请注意用户对暴露内部状态的 map 或 slice 的修改。
// Bad
type Stats struct {
mu sync.Mutex
counters map[string]int
}
// Snapshot 返回当前状态。
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
return s.counters
}
// snapshot 不再受互斥锁保护,可能会引发并发读写的错误
snapshot := stats.Snapshot()
// Good
type Stats struct {
mu sync.Mutex
counters map[string]int
}
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
result := make(map[string]int, len(s.counters))
for k, v := range s.counters {
result[k] = v
}
return result
}
// snapshot 现在是一个拷贝
snapshot := stats.Snapshot()