zoukankan      html  css  js  c++  java
  • golang slice 使用及源码分析

    • 1.先做个小实验
    func main(){
        s1:=make([]int,0,10)
        s1=[]int{1,2,3}
        ss:=make([]int,0,10)
        ss = s1[1:]
        for i:=0;i<len(ss);i++{
            ss[i] +=10
        }
        fmt.Println(s1)   // [1 12 13]
        ss =append(ss,4)
        for i:=0;i<len(ss);i++{
            ss[i] +=10
        }
        fmt.Println(s1)  // [1 12 13] 而不是 [1,22,23]
    
        t:=[]int{0}
        printPoint(t)  // 0xc4200140a8 cap(s)= 1
        t = append(t,1)
        printPoint(t)  // 0xc4200140c0 0xc4200140c8 cap(s)= 2
        t = append(t,2)
        printPoint(t)   // 0xc4200160e0 0xc4200160e8 0xc4200160f0 cap(s)= 4
        t = append(t,3)
        printPoint(t)   //  0xc4200160e0 0xc4200160e8 0xc4200160f0 0xc4200160f8 cap(s)= 4
    }
    
    func printPoint(s []int){
        for i:=0;i<len(s);i++{
            fmt.Print(unsafe.Pointer(&s[i])," ")
    
        }
        fmt.Println("cap(s)=",cap(s))
    }

    发现slice在进行append操作时会跟据原来的slice容量,如果append完成后新slice的容量超过原来slice的容量,则需要扩容,并且将旧的slice数据全部迁移到新的slice开辟的地址里。

    • 2.在runtime目录下找到slice.go,定位到growslice(et *_type, old slice, cap int)这个函数
    type slice struct {
        array unsafe.Pointer
        len   int
        cap   int
    }
    
    // growslice handles slice growth during append.
    // It is passed the slice element type, the old slice, and the desired new minimum capacity,
    // and it returns a new slice with at least that capacity, with the old data
    // copied into it.
    // The new slice's length is set to the old slice's length,
    // NOT to the new requested capacity.
    // This is for codegen convenience. The old slice's length is used immediately
    // to calculate where to write new values during an append.
    // TODO: When the old backend is gone, reconsider this decision.
    // The SSA backend might prefer the new length or to return only ptr/cap and save stack space.
    
    // 与append(slice,s)对应的函数growslice
    // 通过切片的类型,旧切片的容量和数据得出新切片的容量,新切片跟据容量重新申请一块地址,把旧切片的数据拷贝到新切片中
    
    func growslice(et *_type, old slice, cap int) slice {
    
    // 单纯地扩容,不写数据
        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}
        }
    // 扩容规则 1.新的容量大于旧的2倍,直接扩容至新的容量
    // 2.新的容量不大于旧的2倍,当旧的长度小于1024时,扩容至旧的2倍,否则扩容至旧的5/4倍
        newcap := old.cap
        doublecap := newcap + newcap
        if cap > doublecap {
            newcap = cap
        } else {
            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 {
    
    // 为新的切片开辟容量为capmem的地址空间
            p = mallocgc(capmem, nil, false)
    // 将旧切片的数据搬到新切片开辟的地址中
            memmove(p, old.array, lenmem)
            // The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length).
            // Only clear the part that will not be overwritten.
    // 清理下新切片中剩余地址,不能存放堆栈指针
    
    // memclrNoHeapPointers clears n bytes starting at ptr.
    //
    // Usually you should use typedmemclr. memclrNoHeapPointers should be
    // used only when the caller knows that *ptr contains no heap pointers
    // because either:
    //
    // 1. *ptr is initialized memory and its type is pointer-free.
    //
    // 2. *ptr is uninitialized memory (e.g., memory that's being reused
    //    for a new allocation) and hence contains only "junk".
            memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
        } else {
            // Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
            p = mallocgc(capmem, et, true)
            if !writeBarrier.enabled {
                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}
    }
    •  3.slice作为函数参数
    func main(){
        s:=make([]int,0,5)
        s=append(s,1,2,3,4)
        printPoint(s)   // 1 0xc420018120 2 0xc420018128 3 0xc420018130 4 0xc420018138 cap(s)= 5 &s= 0xc42000a060
        processSlice(s)  //11 0xc420018120 12 0xc420018128 13 0xc420018130 14 0xc420018138 11 0xc420018140 cap(s)= 5 &s= 0xc42000a080
    }
    
    func processSlice(ss []int){
        for i:=0;i<len(ss);i++{
            ss[i] +=10
        }
        ss=append(ss,11)
        printPoint(ss)
    }
    func printPoint(s []int){
        for i:=0;i<len(s);i++{
            fmt.Print(s[i],unsafe.Pointer(&s[i])," ")
    
        }
        fmt.Println("cap(s)=",cap(s),"&s=",unsafe.Pointer(&s))
    }

       函数中的形参slice是实参的拷贝,指向切片的指针不同,由于sice没有扩容,函数里面的slice和主函数的实参slice指向的数组地址是一样的

    func main(){
        s:=make([]int,0,4)
        s=append(s,1,2,3,4)
        printPoint(s)  // 1 0xc42008c000 2 0xc42008c008 3 0xc42008c010 4 0xc42008c018 cap(s)= 4 &s= 0xc42008a020
        processSlice(s) // 11 0xc420092000 12 0xc420092008 13 0xc420092010 14 0xc420092018 11 0xc420092020 cap(s)= 8 &s= 0xc42008a040
    
    }
    
    func processSlice(ss []int){
        for i:=0;i<len(ss);i++{
            ss[i] +=10
        }
        ss=append(ss,11)
        printPoint(ss)
    }
    func printPoint(s []int){
        for i:=0;i<len(s);i++{
            fmt.Print(s[i],unsafe.Pointer(&s[i])," ")
    
        }
        fmt.Println("cap(s)=",cap(s),"&s=",unsafe.Pointer(&s))
    }

    函数中的形参slice是实参的拷贝,指向切片的指针不同,由于sice扩容了,函数里面的slice和主函数的实参slice指向的数组地址是不一样的

    • 4.总结
    1. 不要轻易的对切片append,如果新的切片容量比旧的大的话,需要进行growslice操作,新的地址开辟,数据拷贝
    2. 尽量对切片设置初始容量值以避免growslice,类似make([]int,0,100)
    3. 切片是一个结构体,保存着切片的容量,实际长度以及数组的地址
    4. 切片作为函数参数传入会进行引用拷贝,生成一个新的切片,指向同一个数组
  • 相关阅读:
    查询、行hbase Shell之简单命令说明by小雨
    安全模式、磁盘空间Hadoop 常见问题总结by小雨
    [wc2013]糖果公园(70分)by小雨
    方法、脚本Pig Grunt之简单命令及实例说明by小雨
    数据、进程云计算学习笔记Hadoop简介,hadoop实现原理,NoSQL介绍...与传统关系型数据库对应关系,云计算面临的挑战by小雨
    安装、进程云计算学习笔记hadoop的简介,以及安装,用命令实现对hdfs系统进行文件的上传下载by小雨
    配置文件、虚拟机如何使用vagrant在虚拟机安装hadoop集群by小雨
    请求、信息Cloud Foundry中基于Master/Slave机制的Service Gateway——解决Service Gateway单点故障问题by小雨
    格式化、问题ubuntu 12.10下搭建 hadoop 1.0.4 单机和伪分布模式by小雨
    输出、状态hadoop源码TaskAttemptID TaskTrackerAction JobTracker,FileOutputCommitter相关by小雨
  • 原文地址:https://www.cnblogs.com/fwdqxl/p/9317769.html
Copyright © 2011-2022 走看看