Go探索-Slice

Slice

basic

type SliceHeader struct {
	Data uintptr //切片数据域指针,声明了该切片在内存中起始地址
	Len  int
	Cap  int
}

  • 切片内存:在 64 位机器下,最小分配内存单元是 8 字节(内存分配必须是 2 的指数倍),实际切片分配的内存与数据域类型直接相关
    • 切片分配的内存由 3 部分共同组成,data 域大小,len,cap,且每一部分内存宽度必须是 2 的倍数(尽管只使用 1 个字节也会按这样规则分配,为了保证内存对齐)
  • 切片扩容:切片扩容内存会重新分配,扩容后切片元素个数增加,容量改变
  • 扩容容量(元素个数)计算方式:
    • 扩容后容量 newCap 大于 2 倍起始容量(2oldCap),则容量为 newCap
    • 元素个数小于 1024 时,扩容元素个数是直接翻倍;大于 1024 时,新容量是 1.25oldCap
  • 扩容实际分配的内存:
    • 内存= 预估容量 x 元素类型大小

扩容

  • step1: 首先预估扩容为 newCap 后的元素个数
  • step2: 计算 newCap 个元素需要多大 内存= 预估容量 x 元素类型大小
  • step3: 匹配到合适的内存规格(8,16,32,48,96,112...),内存规格是由编程语言提前就从 OS 拿过来的

使用

package main

import (
	"fmt"
)

func main() {
	newStr()

	shareStringArray()
}

func newStr() {
	// new返回值是切片开始地址,但此时并未对切片分配内存,不能直接使用
	ps := new([]string)
	// (*ps)[0] = "eggo" // 并未分配地址,不能使用,运行时会报错 panic: runtime error: index out of range [0] with length 0
	fmt.Printf("addr:%p \n", ps)
	// append 为ps分配了空间
	*ps = append(*ps, "eggo")

	fmt.Printf("addr:%p \n", ps)
	fmt.Println(ps)
}

func shareStringArray() {
	arr := []int{0, 1, 2, 3, 4}
	fmt.Printf("arr扩容前: addr:%p \n", arr)
	fmt.Printf("arr: addr:%p ,value:%v ,len:%d, cap:%d \n", arr, arr, len(arr), cap(arr))
	arr1 := arr[1:3]
	fmt.Printf("arr1: addr:%p ,value:%v ,len:%d, cap:%d \n", arr1, arr1, len(arr1), cap(arr1))
	arr2 := arr[4:]
	arr2[0] = 5
	// note1: 共享同一个底层数组,新截取的切片对底层数据修改会直接影响原始切片
	fmt.Printf("value: arr:%v,arr2:%v\n", arr, arr2)
	fmt.Printf("arr2: addr:%p ,value:%v ,len:%d, cap:%d \n", arr2, arr2, len(arr2), cap(arr2))

	// note2:新切片若扩容,则会重新分配一段内存,然后将原始数据拷贝过来,
	arr2 = append(arr2, 8, 9, 10)
	fmt.Printf("arr扩容后: addr:%p \n", arr)

	// note3:扩容后是新分配内存并没有共享以前的数组,对新切片修改不会影响原始切片
	arr2[0] = 6
	fmt.Printf("arr: addr:%p ,value:%v ,len:%d, cap:%d \n", arr, arr, len(arr), cap(arr))
	fmt.Printf("arr2: addr:%p ,value:%v ,len:%d, cap:%d \n", arr2, arr2, len(arr2), cap(arr2))
}
  1. 切片使用前需要初始化分配内存(或者显示创建切片)

    • 使用 new 创建切片只是返回切片起始地址,实际没有初始化
    • 使用 make 创建切片时,初始化切片地址后会根据声明 len 和 cap 分配内存,若不指定大小,则 len 和 cap 一致
  2. 切片截取

    • 截取切片后会独立为其分配一段内存,len 为截取后的实际元素个数,cap 为截取位置开始到原切片 cap 的差值
    • 截取后的切片起始地址是基于原切片的偏移量计算的,实际上在扩容前,截取后的切片与原始切片是共享底层数组的
    • 截取后切片扩容,则会独立分配一段内存空间,此时新切片和原始切片无关联,对新切片的更改不会对原切片造成影响
  3. 容量计算

    • 切片声明时不指定 cap 大小,则 len 默认和 cap 大小一致
    • 截取后切片的容量=原始切片容量- 切片截取位置处的索引
posted @ 2021-06-10 09:44  雪梨加冰  阅读(56)  评论(0编辑  收藏  举报