关于Golang Slice的append扩容


本文摘自php中文网,作者藏色散人,侵删。

每次 append 操作都会检查 slice 是否有足够的容量,如果足够会直接在原始数组上追加元素并返回一个新的 slice,底层数组不变
而若容量不够,会创建一个新的容量足够的底层数组,先将之前数组的元素复制过来,再将新元素追加到后面,然后返回新的 slice,底层数组改变
而这里对新数组的容量定义是按 乘以2 的机制增加

相关教程推荐:《golang教程》

而今天看到关于 Golang 切片的底层结构即 reflect.SliceHeader 时,发现 append 的扩容并不完全是2倍增长,源码如下(Go version 1.13):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

// grow grows the slice s so that it can hold extra more values, allocating

// more capacity if needed. It also returns the old and new slice lengths.

func grow(s Value, extra int) (Value, int, int) {

    i0 := s.Len()

    i1 := i0 + extra

    if i1 < i0 {

        panic("reflect.Append: slice overflow")

    }

    m := s.Cap()

    if i1 <= m {

        return s.Slice(0, i1), i0, i1

    }

    if m == 0 {

        m = extra

    } else {

        for m < i1 {

            if i0 < 1024 {

                m += m

            } else {

                m += m / 4

            }

        }

    }

    t := MakeSlice(s.Type(), i1, m)

    Copy(t, s)

    return t, i0, i1

}

 

// Append appends the values x to a slice s and returns the resulting slice.

// As in Go, each x's value must be assignable to the slice's element type.

func Append(s Value, x ...Value) Value {

    s.mustBe(Slice)

    s, i0, i1 := grow(s, len(x))

    for i, j := i0, 0; i < i1; i, j = i+1, j+1 {

        s.Index(i).Set(x[j])

    }

    return s

}

首先 Append 判断类型是否 slice,然后调用 grow 扩容,从 l1 <= m 的判断可以发现确实容量足够的情况下,只是对原始数组建立一个新的 slice

但当容量不足时,可以看到只有在当前元素 i0 小于1024时,才是按2倍速度正常,否则其实每次只增长25%,代码验证如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

func main() {

    str := make([]int, 1023)

    fmt.Println(len(str), cap(str))

    str = append(str, 1)

    fmt.Println(len(str), cap(str))

    str = append(str, 1)

    fmt.Println(len(str), cap(str))

}

 

输出:

1023 1023

1024 2048

1025 2048

初始容量已经达到1024后,只增长了256

1

2

3

4

5

6

7

8

9

10

11

12

func main() {

    str := make([]int, 1024)

    fmt.Println(len(str), cap(str))

    str = append(str, 1)

    fmt.Println(len(str), cap(str))

    str = append(str, 1)

    fmt.Println(len(str), cap(str))

}

输出:

1024 1024

1025 1280

1026 1280

当然这里还有个疑惑在于,初始容量为1023时,并不是按1023×2,而是直接1024×2,测试初始500扩容也是直接按512×2,猜测更底层在动态调整时总会补充为2的n次方,目前builtin包下只看到 append 的定义说明,还有待继续挖掘

以上就是关于Golang Slice的append扩容的详细内容,更多文章请关注木庄网络博客!!

相关阅读 >>

2021-03-10 腾讯实习面试

10 golang map的正确使用姿势

golang学习之旅——解开心中的go mod疑惑

golang环境怎么安装

golang map为啥不并发

(一)gof 通过epoll模型管理连接

golang的内存泄漏分析

手撸golang 行为型设计模式 命令模式

go循环队列的实现

go使用iris问题之数字变科学计数法

更多相关阅读请进入《golang》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...