阅读Golang 切片(Slice)底层源码


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

下面由go语言教程栏目给大家介绍Golang 切片(Slice)底层源码,希望对需要的朋友有所帮助!

数组

说切片前先说下数组。数组的两个特性

  • 一段连续内存地址,每个元素都是连续的
  • 元素的类型相同,并且元素个数固定

Go 数组是值类型,赋值和函数传参操作都会复制整个数组数据。

1

2

3

arr := [2]int{1,2}arr2 := arr

fmt.Printf("%p %p",&arr ,&arr2)//切片slice1 := []int{1,2}slice2 := slice1

fmt.Printf("%p %p",slice1 ,slice2)

切片

切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型.切片是一个长度可变的数组。

Slice 的数据结构定义如下:

runtime/slice.go#L13

1

2

3

type slice struct {

    array unsafe.Pointer    len   int

    cap   int}

  • array 就是底层数组的地址
  • len 切片的长度
  • cap 切片的容量

创建切片

src/runtime/slice.go#L83

1

2

3

4

func makeslice(et *_type, len, cap int) unsafe.Pointer {

    mem, overflow := math.MulUintptr(et.size, uintptr(cap))

    ....

    return mallocgc(mem, et, true)}

基本逻辑就是根据容量申请一块内存。

切片扩容

扩容是当切片的长度大于容量的时候,底层数组已经装不下时

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

39

40

41

42

43

44

45

46

47

48

49

50

func growslice(et *_type, old slice, cap int) slice {

    ...

    // 如果新要扩容的容量比原来的容量还要小,直接报panic

    if cap < old.cap {

        panic(errorString("growslice: cap out of range"))

    }

    // 如果当前切片的大小为0,还调用了扩容方法,那么就新生成一个新的容量的切片返回

    // []struct{}

    if et.size == 0 {

        return slice{unsafe.Pointer(&zerobase), old.len, cap}

    }

 

    newcap := old.cap

    doublecap := newcap + newcap    //要扩容的容量大于2 *oldcap 新切片容量 = 该容量

    if cap > doublecap {

        newcap = cap

    } else {

    // 旧容量 小于1024,新容量= 旧容量 * 2 也就是扩容1倍

        if old.cap < 1024 {

            newcap = doublecap        } else {

            // 扩容容量 = 旧容量 +旧容量*1/4

            for 0 < newcap && newcap < cap {

                newcap += newcap / 4

            }

            //溢出之后 新容量=要扩容的容量

            if newcap <= 0 {

                newcap = cap

            }

        }

    }

 

    var overflow bool

    // 计算新的切片的容量,长度。

    var lenmem, newlenmem, capmem uintptr

 

    ....

 

    var p unsafe.Pointer    if et.ptrdata == 0 {

        p = mallocgc(capmem, nil, false)

        memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)

    } else {

        p = mallocgc(capmem, et, true)

        if lenmem > 0 && writeBarrier.enabled {

            bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata)

        }

    }

    //移动到p

    memmove(p, old.array, lenmem)

    //返回slice结构,让slice.array指向p

    return slice{p, old.len, newcap}}

  • 新申请容量cap,如果大于2倍旧容量(oldcap),要扩容的容量(newcap)=新申请容量cap
  • 如果旧容量(oldcap)< 1024, 要扩容的容量(newcap)在旧容量(oldcap)基础上扩容1倍,否则则扩容 1/4
  • 如果数值溢出,要扩容的容量 = 新申请的容量

1

2

3

4

5

6

arr := make([]int,1024)

arr = append(arr,1)

fmt.Println(len(arr),cap(arr))// 1025,1280

arr1 := make([]int,10)

arr1 = append(arr1,1)

fmt.Println(len(arr1),cap(arr1))//11 20

  • 注意事项: 切片共享底层数组,所以在切片赋值的时候,修改切片会导致底层数组改变,而产生BUG

1

2

3

4

5

6

7

8

9

arr := []int{1,2,3,4}

arr1 := arr[:2] //[1,2]

arr1 = append(arr1,5)

fmt.Println(arr[3]) //5 修改了底层数组

//例子2

arr3 := []int{1,2,3,4}

arr4 := arr3[2:]

arr4 = append(arr4,10)//扩容 不会影响arr3

fmt.Println(arr3)

切片复制

src/runtime/slice.go#L247

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

//toPtr 目标地址 toLen目标长度

// width 元素大小

func slicecopy(toPtr unsafe.Pointer, toLen int, fromPtr unsafe.Pointer, fromLen int, width uintptr) int {

    //判断长度

    if fromLen == 0 || toLen == 0 {

        return 0

    }

    n := fromLen

    if toLen < n {

        n = toLen

    }

    //切片大小等于0

    if width == 0 {

        return n

    }

 

    size := uintptr(n) * width

    //特殊处理 如果只有一个元素并且大小是1byte,那么指针直接转换即可

    if size == 1 {

        *(*byte)(toPtr) = *(*byte)(fromPtr)

    } else {

        //从 fm.array 地址开始,拷贝到 to.array 地址之后

        memmove(toPtr, fromPtr, size)

    }

    return n

}

以上就是阅读Golang 切片(Slice)底层源码的详细内容,更多文章请关注木庄网络博客!!

相关阅读 >>

golang学习笔记for循环语句

go-zero 如何扛住流量冲击(二)

go语言sync包的学习(mutex、waitgroup、cond)

24 goroutine channel实现并发和并行(一)

golang | 一文带你快速入门context

golang怎样读取json数据

go orm 干啥的?

golang指针传递和值传递的区别是什么?

golang select不阻塞吗

身份证号验证库

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




打赏

取消

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

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

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

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

评论

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