• 技术文章 >后端开发 >Golang

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

    藏色散人藏色散人2021-03-03 15:51:36转载571

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

    数组

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

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

    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

    type slice struct {
        array unsafe.Pointer    len   int
        cap   int}

    创建切片

    src/runtime/slice.go#L83

    func makeslice(et *_type, len, cap int) unsafe.Pointer {
        mem, overflow := math.MulUintptr(et.size, uintptr(cap))
        ....
        return mallocgc(mem, et, true)}

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

    切片扩容

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

    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}}
      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
    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

    //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)底层源码的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:learnku,如有侵犯,请联系admin@php.cn删除
    专题推荐:golang go go教程
    上一篇:golang怎么编写PHP扩展 下一篇:go语言能做web吗
    大前端线上培训班

    相关文章推荐

    • 关于用golang封装ssh用于在远程主机上执行命令,上传或下载文件• 分享一款golang style语法的golang orm库• Golang的memcache如何简单实现• 分享一个Golang Http 验证码示例• 详解golang.org/x一键安装脚本

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网