切片是非常常用的数据类型,属于容器类型。每一个切片背后都有一个数组作为支撑。以下代码和图片来自于g101,很好的诠释了切片的内部结构。
type _slice struct {
elements unsafe.Pointer // referencing underlying elements
len int // length
cap int // capacity
}
len 表示切片存储元素的数量,cap 表示切片的容量。下面这张图展示了切片的内存布局。
一,插入切片元素
大体上分为三步,
- 确保切片有足够的空间来容纳新增的元素,一般利用内置函数 append,在结尾添加一个零值元素。例如,添加一个类型为T的元素:slice_data = append(slice_data, T{})
- 找到插入元素的位置,将后面所有元素后移一个位置。一般利用内置函数 copy 来操作切片自身。例如,插入位置i-th,copy(slice_data[i+1:], slice_data[i:])
- 将待插入的元素赋值到切片的插入位置。例如,slice_data[i] = T1 (类型为 T)
copy 是 golang 的内置函数,具体定义如下:
// The copy built-in function copies elements from a source slice into a
// destination slice. (As a special case, it also will copy bytes from a
// string to a slice of bytes.) The source and destination may overlap. Copy
// returns the number of elements copied, which will be the minimum of
// len(src) and len(dst).
func copy(dst, src []Type) int
从其说明中,可以看出能够操作切片自身,非常方便。
二,删除切片元素
有很多种删除切片元素的方法,简单常用的主要有两种。
删除后的切片,需要保持原有的顺序。那么被删除的元素后面的所有元素都需要向前移动一个位置。例如,删除 i-th 元素,
slice_data = append(slice_data[:i], slice_data[i+1:]...)
删除后的切片,不需要保持原有的顺序。可以将末尾的元素移到被删除的元素的位置上。例如,删除 i-th 元素,
slice_data[i] = slice_data[len(slice_data - 1]
slice_data = slice_data[:len(slice_data) - 1]
为了避免原末尾的元素造成内存泄露,零值化其:
slice_data[:len(slice_data)+1][len(slice_data)] = T0 (T 类型零值)
三,简单的例子实现切片插入和删除
以下是一个实例代码来展示切片的操作。在日常工作中,我们对切片的操作主要是插入一次,查询多次,这样的话,经过排序的切片,就会有较好的性能。具体代码如下:
// Sample...
type pair struct {
key string
value []byte
}
type pairs []pair
// Load retrieves the value of a key/value pair.
// The given Pair must be a pointer and will
// be set to the value via its key.
func (p pairs) Load(one *pair) error {
i := sort.Search(len(p), func(i int) bool { return p[i].key >= one.key })
if i < len(p) && p[i].key == one.key {
one.value = copyBytes(p[i].value)
return nil
}
return &KeyError{Key: one.key, Err: errors.New("no such key in pairs")}
}
// Set adds or updates the given Pair in the pairs list.
func (p *pairs) Set(one pair) {
// better make a copy to pair so not mess anything
// to pair-> value, but not if pair-> value it too big
// and won't be changed anymore.
i := sort.Search(len(*p), func(i int) bool { return (*p)[i].key >= one.key })
switch {
case i < len(*p) && (*p)[i].key == one.key:
// pair is present at i-th element of pairs
(*p)[i].value = copyBytes(one.value)
case i < len(*p):
// insert pair before i-th element of pairs
// total 3 steps:
// 1. append zero value of pair
*p = append(*p, pair{})
// 2. existing elements of pairs must be shifted
copy((*p)[i+1:], (*p)[i:])
// 3. set the pair at the i-th element
(*p)[i].key = one.key
(*p)[i].value = copyBytes(one.value)
default:
// pair should be placed at the end of pairs
*p = append(*p, pair{key: one.key, value: copyBytes(one.value)})
}
}
// Delete deletes the value of a key/value pair.
// The given Pair must be a pointer
func (p *pairs) Delete(one *pair) {
i := sort.Search(len(*p), func(i int) bool { return (*p)[i].key >= one.key })
if i < len(*p) && (*p)[i].key == one.key {
*p = append((*p)[:i], (*p)[i+1:]...)
}
}
func copyBytes(src []byte) []byte {
if src == nil {
return nil
}
dst := make([]byte, len(src))
copy(dst, src)
return dst
}
type KeyError struct {
Key string
Err error
}
func (e *KeyError) Error() string {
return fmt.Sprintf("Pair-> key %q: %v", e.Key, e.Err)
}
简单的 main 函数如下:
func SliceOp() error {
cities := pairs([]pair{
pair{
"BJ",
[]byte("Beijing"),
},
pair{
"SH",
[]byte("Shanghai"),
},
})
// set a new one
dalian := pair{
"DL",
[]byte("Dalian"),
}
cities.Set(dalian)
fmt.Println("Pair List:")
for _, c := range cities {
fmt.Println(" ", c.key, " - ", string(c.value))
}
// load one
r := pair{key: "BJ"}
err := cities.Load(&r)
if err != nil {
return err
}
fmt.Println("\nRetrieve:", "\n Key:", r.key, "\n Values:", string(r.value))
// delete one
cities.Delete(&r)
fmt.Println("\nAfter deleting, Pair List:")
for _, c := range cities {
fmt.Println(" ", c.key, " - ", string(c.value))
}
return nil
}
输出结果如下:
Pair List:
BJ - Beijing
DL - Dalian
SH - Shanghai
Retrieve:
Key: BJ
Values: Beijing
After deleting, Pair List:
DL - Dalian
SH - Shanghai
参考链接: