深入理解 golang 切片机制(理)

原文

  • 切片总是要有一个交代的,来看看这段代码code
package main

import (
    "fmt"
    "unsafe"
)

type Slice struct {
    ptr unsafe.Pointer // Array pointer
    len int            // slice length
    cap int            // slice capacity
}

// 因为需要指针计算,所以需要获取int的长度
// 32位 int length = 4
// 64位 int length = 8
var intLen = int(unsafe.Sizeof(int(0)))

func main() {
    s := make([]int, 10, 20)

    // 利用指针读取 slice memory 的数据
    if intLen == 4 { // 32位
        m := *(*[4 + 4*2]byte)(unsafe.Pointer(&s))
        fmt.Println("slice memory:", m)
    } else { // 64 位
        m := *(*[8 + 8*2]byte)(unsafe.Pointer(&s))
        fmt.Println("slice memory:", m)
    }

    // 把slice转换成自定义的 Slice struct
    slice := (*Slice)(unsafe.Pointer(&s))
    fmt.Println("slice struct:", slice)
    fmt.Printf("ptr:%v len:%v cap:%v \n", slice.ptr, slice.len, slice.cap)
    fmt.Printf("golang slice len:%v cap:%v \n", len(s), cap(s))

    s[0] = 0
    s[1] = 1
    s[2] = 2

    // 转成数组输出
    arr := *(*[3]int)(unsafe.Pointer(slice.ptr))
    fmt.Println("array values:", arr)

    // 修改 slice 的 len
    slice.len = 15
    fmt.Println("Slice len: ", slice.len)  
    fmt.Println("golang slice len: ", len(s))  
}
  • 看出什么来了么?
    • 储备知识:
      • unsafe.Pointer 类似于c语言中的void*
      • unsafe.Pointer所占空间 = int 类型所占字节数
    • 结论:
      • slice的结构等同于Slice结构体
// 每次cap改变,指向array的ptr就会变化一次
s := make([]int, 1)

fmt.Printf("len:%d cap: %d array ptr: %v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))

for i := 0; i < 5; i++ {
    s = append(s, i)
    fmt.Printf("len:%d cap: %d array ptr: %v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))
}

fmt.Println("Array:", s)
  • 替换上面的主程序,你发现了什么呢?
    • 每次append,地址都会发生变化 ,说明当cap不够的时候,会产生复制操作。
    • 实际go在append的时候放大cap是有规律的。在 cap 小于1024的情况下是每次扩大到 2 * cap ,当大于1024之后就每次扩大到 1.25 * cap 。所以上面的测试中cap变化是 1, 2, 4, 8
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容