Golang-Slice

萌小A · · 2674 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

1.相关概念

1.开篇

最近忙着看加密,以太坊的代码。今天打算换个口味想有必要在把Go的进阶一下。毕竟最近都在用Golang 但是 Golang的底层都没有接触过。突然想起来暑假在公司上班的时候有人问我slice的一些用法:为什么作为参数的时候,直接修改会对原有的值有改变,而用了append以后就还是保留原来的样子。我的回答就是 在golang里面对array,slice,map的操作全部都是指针操作,所以第一种是直接修改了指定地址里面的数据,第二种append的话会自己产生一个新的slice所以对本来的数据没有影响。基本的测试代码大概这样的:

func main()  {
    a:=[]int{1,2,3,4,5,6}
    test(a)
    fmt.Println(a) //=>输出[10,2,3,4,5,6]
}

func test(input []int){
    input[0]=10
}

func main()  {
    a:=[]int{1,2,3,4,5,6}
    test2(a)
    fmt.Println(a) =>  输出[1,2,3,4,5,6]
}
func test2(input []int){

    input = append(input,10)
}

一直想深追一下底层代码看下是不是跟我理解的相同,由于当时上班太忙把这件事都给忘了,今天刚好周五 又想起来了这件事所以就想深究下golang的底层操作。

2.slice的构成

slice跟array的区别就是,slice是一个可以扩容的array,有点类似java中的arraylist。挺有必要去看下slice的结构和一些方法的,重点看append。首先先找到slice的源码(找了挺久: gopath/src/runtime/slice.go)

type slice struct {
    array unsafe.Pointer   //=>指向一个array
    len   int
    cap   int
}

slice的结构体里面也就3个参数,array,len,cap。 array就是一个指针指向数组的一个指针。也就是说slice其实是对array指针操作的一个封装而已。
len当前slice的长度,cap当前slice的容量(cap>=len)

2.Slice

1.创建

func makeslice(et *_type, len, cap int) slice {
    // NOTE: The len > maxElements check here is not strictly necessary,
    // but it produces a 'len out of range' error instead of a 'cap out of range' error
    // when someone does make([]T, bignumber). 'cap out of range' is true too,
    // but since the cap is only being supplied implicitly, saying len is clearer.
    // See issue 4085.
    maxElements := maxSliceCap(et.size)
    if len < 0 || uintptr(len) > maxElements {
        panic(errorString("makeslice: len out of range"))
    }

    if cap < len || uintptr(cap) > maxElements {
        panic(errorString("makeslice: cap out of range"))
    }

    p := mallocgc(et.size*uintptr(cap), et, true)
    return slice{p, len, cap}
}

主要做一些长度和容量的判断这些到底无所谓,最后看
p := mallocgc(et.size*uintptr(cap), et, true)
return slice{p, len, cap}
也就是开辟了一块地址,p指向这个地址,最后生成了slice。 所以说slice就是对array指针操作的一层封装
这个方面的下面还有64位的操作,最后也是调用了这个方法

2.append

先解释下append的作用就是在slice的最后一个元素在添加内容。就是链表里面的加加加加加。追了好久 append最后的实现= =, 由于append 内建函数所以不提供源码,反正网上找来找去,找到了一些信息 最后追踪到:

//参数为 类型,以前的slice 和最小需要的容量 
func growslice(et *_type, old slice, cap int) slice {
       //raceenabled 和msanenabled 都为false 直接略过
    if raceenabled {
        callerpc := getcallerpc(unsafe.Pointer(&et))
        racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
    }
    if msanenabled {
        msanread(old.array, uintptr(old.len*int(et.size)))
    }

    if et.size == 0 {
        if cap < old.cap {
            panic(errorString("growslice: cap out of range"))
        }
        // append should not create a slice with nil pointer but non-zero len.
        // We assume that append doesn't need to preserve old.array in this case.
        return slice{unsafe.Pointer(&zerobase), old.len, cap}
    }
    
//新的容量的规则
    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
//如果当前的长度小于1024 那么就直接翻倍,否则增加的量会比较小
        if old.len < 1024 {
            newcap = doublecap
        } else {
            for newcap < cap {
                newcap += newcap / 4
            }
        }
    }
      //所有元素的长度,新的所有元素的长度 和容量下元素的长度
    var lenmem, newlenmem, capmem uintptr
    const ptrSize = unsafe.Sizeof((*byte)(nil))
    switch et.size {
    case 1:
        lenmem = uintptr(old.len)
        newlenmem = uintptr(cap)
        capmem = roundupsize(uintptr(newcap))
        newcap = int(capmem)
    case ptrSize:
        lenmem = uintptr(old.len) * ptrSize
        newlenmem = uintptr(cap) * ptrSize
        capmem = roundupsize(uintptr(newcap) * ptrSize)
        newcap = int(capmem / ptrSize)
    default:
        lenmem = uintptr(old.len) * et.size. //原有的长度*元素的长度
        newlenmem = uintptr(cap) * et.size //最小容量的长度
        capmem = roundupsize(uintptr(newcap) * et.size)//容量下的长度
        newcap = int(capmem / et.size)//新容量
    }
  
    if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) {
        panic(errorString("growslice: cap out of range"))
    }

    var p unsafe.Pointer
    if et.kind&kindNoPointers != 0 {
        p = mallocgc(capmem, nil, false) //申请新的长度地址
   
        memmove(p, old.array, lenmem)
       
        memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
    } else {
        p = mallocgc(capmem, nil, false) //申请新的长度地址
   
        p = mallocgc(capmem, et, true)
        if !writeBarrier.enabled {
            //  复制 n bytes from "from" to "to". 第一参数为to
            memmove(p, old.array, lenmem)
        } else {
            for i := uintptr(0); i < lenmem; i += et.size {
                            //复制原来的值
                typedmemmove(et, add(p, i), add(old.array, i))
            }
        }
    }
   //新的切片产生
    return slice{p, old.len, newcap}
}

growslice 也就是给slice 扩容的,重要的代码都加上注释,也就是可以发现 是产生了一个新的slice 也就是新的array的指针跟新的长度和大小,那接下来就是往后面添加的东西了。讲真slice这方面有点像java的string,java的string的append 也是产生新的string之类的。
好的好的。就分析到这 biu biu biu 也好久没有写技术博客了,今天稍微写一下。有哪里说错的欢迎指出
之后还想写一些有关go高阶的东西,想想写什么比较好。。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:萌小A

查看原文:Golang-Slice

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2674 次点击  
加入收藏 微博
1 回复  |  直到 2021-09-13 16:28:45
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传