关于golang:Go进阶数据结构Slice

9次阅读

共计 1256 个字符,预计需要花费 4 分钟才能阅读完成。

slice 又称动静数组,依靠数组实现,但比数组更灵便,在 Go 语言中个别都会应用 slice 而非 array。

个性

申明和初始化 slice 的形式除了从数组中间接切取外,次要还有以下几种:变量申明、字面量申明、应用内置函数 new() 和 make()。应用变量申明和 new() 函数后的 slice 的值为 nil,对其应用 append() 追加元素时会导致异样,个别举荐应用 make() 函数来初始化。

实现原理

数据结构

源码 src/runtime/slice.go:slice 定义了 slice 的数据结构:

type slice struct {
    array unsafe.Pointer // 底层数组指针
    len   int            // 长度
    cap   int            // 容量
}

相干操作

创立

当咱们应用 make() 创立 slice 时,能够同时指定长度和容量,底层会调配一个数组,数组的长度即容量。

例如,slice := make([]int, 5, 10) 所创立的 slice,构造如下所示:

扩容

向 slice 追加元素时,如果 slice 的容量不足以包容追加的元素时,将会触发扩容。扩容实际上是重新分配一块更大的内存,将原 slice 拷贝进新 slice,再将数据追加进去。这也是为什么咱们常常能见到相似于 s := append(s, 1) 这种写法的起因。

扩容时容量的变动遵循以下根本规定:

  • 原 slice 容量小于 1024,则新 slice 的容量扩充为原来的 2 倍;
  • 原 slice 容量大于等于 1024,则 新 slice 的容量扩充为原来的 1.25 倍。

不过,理论过程中还会综合思考元素的类型和内存调配策略,在该规定的根底上做一些微调。

上面有一段代码示例,察看函数的输入,联合本局部内容,能够很好地明确扩容的外部机制:

package main

import "fmt"

func SliceRise(s []int) {s = append(s, 0)
    for i := range s {s[i]++
    }
}

func main() {s1 := []int{1, 2}
    s2 := s1
    s2 = append(s2, 3)
    SliceRise(s1)
    SliceRise(s2)
    fmt.Println(s1, s2)
}
拷贝

拷贝两个 slice 时,会将源 slice 的元素一一拷贝到目标 slice 指向的底层数组中,拷贝数量取决于两个 slice 长度的较小值。例如长度为 10 的 slice 拷贝到长度为 5 的 slice 中时,只会拷贝 5 个元素,过程中并不会触发扩容。

一些 Tips

咱们常说 slice 是所谓的援用类型,次要就是因为它的构造外部含有底层数组的指针,因而在函数外部批改 slice 中的元素的话,内部变量也会受到影响。

咱们能够应用 a[low:high] 表达式切取字符串,此时会产生新的字符串,用这个办法能够疾速截取字符串。

如果咱们是从字符串或数组上切取 slice 的话,表达式 a[low:high] 需满足:0 <= low <= high <= len(a),如果不满足则会触发 panic。然而如果切取对象是另一个 slice 的话,low 和 high 的最大取值就不是 a 的长度,而是 a 的容量。在理论应用中,咱们须要留神这一点。

正文完
 0