切片是非常常用的数据类型,属于容器类型。每一个切片背后都有一个数组作为支撑。以下代码和图片来自于g101,很好的诠释了切片的内部结构。
type _slice struct {elements unsafe.Pointer // referencing underlying elementslen int // lengthcap 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 stringvalue []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 shiftedcopy((*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 pointerfunc (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 stringErr 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 onedalian := pair{"DL",[]byte("Dalian"),}cities.Set(dalian)fmt.Println("Pair List:")for _, c := range cities {fmt.Println(" ", c.key, " - ", string(c.value))}// load oner := 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 onecities.Delete(&r)fmt.Println("\nAfter deleting, Pair List:")for _, c := range cities {fmt.Println(" ", c.key, " - ", string(c.value))}return nil}
输出结果如下:
Pair List:BJ - BeijingDL - DalianSH - ShanghaiRetrieve:Key: BJValues: BeijingAfter deleting, Pair List:DL - DalianSH - Shanghai
参考链接:
