后端技术总结

  • 首页
  • github
  • About Me
  1. 首页
  2. Golang
  3. 正文

golang之从源码角度看slice的len和cap

2022年1月3日 1091点热度 0人点赞 0条评论
内容纲要

举例

len和cap的关系我们举个恰当的例子来看就是cap比作一个水瓶,len比作这个水瓶中此时所装载水的容量,而当瓶子大小装不下水的时候,也就是这个slice的扩容append我们暂时不讨论。
我们直接先从一道关于slice的题来开始:

func main() {
    var array [20]int
    fmt.Println(array)
    slice1 := array[10:11]
    fmt.Println(slice1)
    fmt.Println(len(slice1))
    fmt.Println(cap(slice1))
}

大家可以先在不看答案的情况下来计算一下输出结果,下面是结果

[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0]
1
10

不知大家看出上面的结果没,为何是上面这几个输出结果呢?这里让我们尝试从源码的角度来看看输出这个结果的原因。

说明

原码路径:$GOROOT/src/reflect/value.go

// Slice returns v[i:j].
// It panics if v's Kind is not Array, Slice or String, or if v is an unaddressable array,
// or if the indexes are out of bounds.
func (v Value) Slice(i, j int) Value {
    var (
        cap  int
        typ  *sliceType
        base unsafe.Pointer
    )
    switch kind := v.kind(); kind {
    // ... 省略
    case Array:
        // ... 省略
        tt := (*arrayType)(unsafe.Pointer(v.typ))
        cap = int(tt.len) // 把数组的长度当做cap
        typ = (*sliceType)(unsafe.Pointer(tt.slice))
        base = v.ptr
    case Slice:
        typ = (*sliceType)(unsafe.Pointer(v.typ))
        s := (*unsafeheader.Slice)(v.ptr)
        base = s.Data
        cap = s.Cap // 如果为slice,则直接取其中结构体中的cap
    case String: 
        // 这个里面的大致思路就是直接取了字符串中的一段数据拷贝后返回回去
        // ... 省略
    }

    // Declare slice so that gc can see the base pointer in it.
    var x []unsafe.Pointer

    // Reinterpret as *unsafeheader.Slice to edit.
    s := (*unsafeheader.Slice)(unsafe.Pointer(&x))
    s.Len = j - i   // 统一的,不管是array还是slice,len = j - i
    s.Cap = cap - i // 由上面计算出来的cap - i 就是新的cap大小
    if cap-i > 0 {
        s.Data = arrayAt(base, i, typ.elem.Size(), "i < cap")
    } else {
        // do not advance pointer, to avoid pointing beyond end of slice
        s.Data = base
    }

    fl := v.flag.ro() | flagIndir | flag(Slice)
    return Value{typ.common(), unsafe.Pointer(&x), fl}
}

针对上面代码的一些说明就是:

  • 通过reflect映射Value的类型,然后计算对应的cap值
    • 如果映射为Array类型,则cap大小就等于数组的长度
    • 如果映射为Slice类型,则cap大小就等于切片结构体中的cap
    • 如果为映射String类型,则直接取这个string中指定长度赋值给一个新的Value对象后返回
  • 经过上面的操作后,原本的Array或者是Slice都会被统一转换为slice,新的slice的len就是参数的j-i,cap就是参数cap-i,而指向的数据都是原本的内存空间。

经过上面原码的简单说明后,我们再回到上面举例里面的打印输出:

func main() {
    var array [20]int
    fmt.Println(array) // Output: 这里输出20个0,相信大家问题不大
    slice1 := array[10:11]
    fmt.Println(slice1) // Output: 经过上面的原码来看,slice1的len=11-10=1,cap=20-10=10,所以输出为[0]
    fmt.Println(len(slice1)) // Output: 上面给出了解释,输出为 len=11-10=1
    fmt.Println(cap(slice1)) // Output: 上面给出了解释,输出为 cap=20-10=10
}

至此,我们对上面的例子说明有了一定的原码支撑,但是,我们有时也会遇到这种用法:array[10:11:20],那后面那个20又怎么解释呢?其实这个的原码也在上面提到的那个文件中,在上面提到的文件直接搜索func (v Value) Slice3(i, j, k int) Value,我这里就不列举原码了,这里面i和j的解释说明同Slice,k的含义也就是我们给他指定了一个cap,这个k的大小不能大于它原始的cap值,超过的话会直接报panic,然后剩下的流程几乎同Slice的原理(不支持String类型,否则也会panic)。

标签: 暂无
最后更新:2022年1月3日

PingD

反思让我成长

点赞
< 上一篇

文章评论

取消回复

PingD

反思让我成长

归档
  • 2023年1月
  • 2022年3月
  • 2022年1月
  • 2021年10月
  • 2021年8月
  • 2021年4月
  • 2020年11月
  • 2020年10月
  • 2020年9月
  • 2020年8月
  • 2019年9月
  • 2019年7月
  • 2019年6月
  • 2019年4月
  • 2019年3月
最新 热点 随机
最新 热点 随机
断更了,不想写了 新路由2(newifi d1) 小白刷老毛子固件 2021年度总结 golang之从源码角度看slice的len和cap 基于chan实现的简易协程池及说明 订单超时方案(初稿)
golang之从源码角度看slice的len和cap Go json裁剪 Golang之HTTP EOF/connection reset by peer详解 sh、bash和zsh区别 记踩Etcd的一个坑 Golang之go get golang.org/x timeout
最近评论

COPYRIGHT © 2021 dpjeep.com. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS

蜀ICP备18036663号-1