切片是非常常用的数据类型,属于容器类型。每一个切片背后都有一个数组作为支撑。以下代码和图片来自于g101,很好的诠释了切片的内部结构。

  1. type _slice struct {
  2. elements unsafe.Pointer // referencing underlying elements
  3. len int // length
  4. cap int // capacity
  5. }

len 表示切片存储元素的数量,cap 表示切片的容量。下面这张图展示了切片的内存布局。
slice-internal2.png

一,插入切片元素

大体上分为三步,

  1. 确保切片有足够的空间来容纳新增的元素,一般利用内置函数 append,在结尾添加一个零值元素。例如,添加一个类型为T的元素:slice_data = append(slice_data, T{})
  2. 找到插入元素的位置,将后面所有元素后移一个位置。一般利用内置函数 copy 来操作切片自身。例如,插入位置i-th,copy(slice_data[i+1:], slice_data[i:])
  3. 将待插入的元素赋值到切片的插入位置。例如,slice_data[i] = T1 (类型为 T)

copy 是 golang 的内置函数,具体定义如下:

  1. // The copy built-in function copies elements from a source slice into a
  2. // destination slice. (As a special case, it also will copy bytes from a
  3. // string to a slice of bytes.) The source and destination may overlap. Copy
  4. // returns the number of elements copied, which will be the minimum of
  5. // len(src) and len(dst).
  6. func copy(dst, src []Type) int

从其说明中,可以看出能够操作切片自身,非常方便。

二,删除切片元素

有很多种删除切片元素的方法,简单常用的主要有两种。

  1. 删除后的切片,需要保持原有的顺序。那么被删除的元素后面的所有元素都需要向前移动一个位置。例如,删除 i-th 元素,

    1. slice_data = append(slice_data[:i], slice_data[i+1:]...)
  2. 删除后的切片,不需要保持原有的顺序。可以将末尾的元素移到被删除的元素的位置上。例如,删除 i-th 元素,

    1. slice_data[i] = slice_data[len(slice_data - 1]
    2. slice_data = slice_data[:len(slice_data) - 1]

    为了避免原末尾的元素造成内存泄露,零值化其:

    1. slice_data[:len(slice_data)+1][len(slice_data)] = T0 (T 类型零值)

三,简单的例子实现切片插入和删除

以下是一个实例代码来展示切片的操作。在日常工作中,我们对切片的操作主要是插入一次,查询多次,这样的话,经过排序的切片,就会有较好的性能。具体代码如下:

  1. // Sample...
  2. type pair struct {
  3. key string
  4. value []byte
  5. }
  6. type pairs []pair
  7. // Load retrieves the value of a key/value pair.
  8. // The given Pair must be a pointer and will
  9. // be set to the value via its key.
  10. func (p pairs) Load(one *pair) error {
  11. i := sort.Search(len(p), func(i int) bool { return p[i].key >= one.key })
  12. if i < len(p) && p[i].key == one.key {
  13. one.value = copyBytes(p[i].value)
  14. return nil
  15. }
  16. return &KeyError{Key: one.key, Err: errors.New("no such key in pairs")}
  17. }
  18. // Set adds or updates the given Pair in the pairs list.
  19. func (p *pairs) Set(one pair) {
  20. // better make a copy to pair so not mess anything
  21. // to pair-> value, but not if pair-> value it too big
  22. // and won't be changed anymore.
  23. i := sort.Search(len(*p), func(i int) bool { return (*p)[i].key >= one.key })
  24. switch {
  25. case i < len(*p) && (*p)[i].key == one.key:
  26. // pair is present at i-th element of pairs
  27. (*p)[i].value = copyBytes(one.value)
  28. case i < len(*p):
  29. // insert pair before i-th element of pairs
  30. // total 3 steps:
  31. // 1. append zero value of pair
  32. *p = append(*p, pair{})
  33. // 2. existing elements of pairs must be shifted
  34. copy((*p)[i+1:], (*p)[i:])
  35. // 3. set the pair at the i-th element
  36. (*p)[i].key = one.key
  37. (*p)[i].value = copyBytes(one.value)
  38. default:
  39. // pair should be placed at the end of pairs
  40. *p = append(*p, pair{key: one.key, value: copyBytes(one.value)})
  41. }
  42. }
  43. // Delete deletes the value of a key/value pair.
  44. // The given Pair must be a pointer
  45. func (p *pairs) Delete(one *pair) {
  46. i := sort.Search(len(*p), func(i int) bool { return (*p)[i].key >= one.key })
  47. if i < len(*p) && (*p)[i].key == one.key {
  48. *p = append((*p)[:i], (*p)[i+1:]...)
  49. }
  50. }
  51. func copyBytes(src []byte) []byte {
  52. if src == nil {
  53. return nil
  54. }
  55. dst := make([]byte, len(src))
  56. copy(dst, src)
  57. return dst
  58. }
  59. type KeyError struct {
  60. Key string
  61. Err error
  62. }
  63. func (e *KeyError) Error() string {
  64. return fmt.Sprintf("Pair-> key %q: %v", e.Key, e.Err)
  65. }

简单的 main 函数如下:

  1. func SliceOp() error {
  2. cities := pairs([]pair{
  3. pair{
  4. "BJ",
  5. []byte("Beijing"),
  6. },
  7. pair{
  8. "SH",
  9. []byte("Shanghai"),
  10. },
  11. })
  12. // set a new one
  13. dalian := pair{
  14. "DL",
  15. []byte("Dalian"),
  16. }
  17. cities.Set(dalian)
  18. fmt.Println("Pair List:")
  19. for _, c := range cities {
  20. fmt.Println(" ", c.key, " - ", string(c.value))
  21. }
  22. // load one
  23. r := pair{key: "BJ"}
  24. err := cities.Load(&r)
  25. if err != nil {
  26. return err
  27. }
  28. fmt.Println("\nRetrieve:", "\n Key:", r.key, "\n Values:", string(r.value))
  29. // delete one
  30. cities.Delete(&r)
  31. fmt.Println("\nAfter deleting, Pair List:")
  32. for _, c := range cities {
  33. fmt.Println(" ", c.key, " - ", string(c.value))
  34. }
  35. return nil
  36. }

输出结果如下:

  1. Pair List:
  2. BJ - Beijing
  3. DL - Dalian
  4. SH - Shanghai
  5. Retrieve:
  6. Key: BJ
  7. Values: Beijing
  8. After deleting, Pair List:
  9. DL - Dalian
  10. SH - Shanghai

参考链接: