您的位置:首页 > 汽车 > 新车 > Go 基础丨切片 slice

Go 基础丨切片 slice

2024/7/2 15:17:28 来源:https://blog.csdn.net/Hedon954/article/details/139751050  浏览:    关键词:Go 基础丨切片 slice

1. 底层

  • runtime/slice.go

    type slice struct {array unsafe.Pointer		// 指向底层数组len   int								// 切片元素数量cap   int								// 底层数组容量
    }
    
  • reflect/value.go

    type SliceHeader struct {Data uintptrLen  intCap  int
    }
    

2. 创建

  • 根据数组创建

    s := arr[0:3]
    
  • 字面量:编译时插入创建数组的代码

    s := []int{1, 2, 3}
    
  • make:运行时创建数组

    s := make([]int, 10)
    

3. 测试

3.1 字面量创建切片底层

func main() {s := []int{1, 2, 3}fmt.Println(s)
}

查看 Plan9 汇编代码,运行:

go build -gcflags -S main.go

重点关注 s := []int{1,2,3} 对应的部分:

LEAQ    type.[3]int(SB), AX						#创建一个大小为3,类型为int的数组
PCDATA  $1, $0
NOP
CALL    runtime.newobject(SB)				  #新建一个结构体(slice)的值,并往里面塞3个数组
MOVQ    $1, (AX)
MOVQ    $2, 8(AX)
MOVQ    $3, 16(AX)

3.2 make 创建切片

func main() {s := make([]int, 3)fmt.Println(s)
}

查看 Plan9 汇编代码,运行:

go build -gcflags -S main.go

重点关注 s := make([]int, 3) 对应的部分:

LEAQ    type.int(SB), AX
MOVL    $3, BX
MOVQ    BX, CX
PCDATA  $1, $0
CALL    runtime.makeslice(SB) 		#直接调用 makeslice 方法

4. 访问

  • 下标访问
  • range 遍历
  • len 查看切片长度
  • cap 查看数组容量

5. 追加

s := []int{1, 2, 3}
s = append(s, 4, 5)

如果 append 后,len > cap,则需要做扩容。

6. 扩容

底层调用 growslice() 方法:

6.1 Go1.17 及之前的 growslice()

func growslice(et *_type, old slice, cap int) slice {// 检查...// 确定新 capnewcap := old.capdoublecap := newcap + newcapif cap > doublecap {// ① 如果新 cap 大于两倍旧 cap,则直接使用新 capnewcap = cap} else {// ② 如果新 cap 小于两倍旧 cap 且旧 cap 小于 1024,则 cap 直接翻倍if old.cap < 1024 {newcap = doublecap} else {// ③ 如果新 cap 小于两倍旧 cap 且旧 cap 大于 1024,则每次增长 25%for 0 < newcap && newcap < cap {newcap += newcap / 4}if newcap <= 0 {newcap = cap}}}// 新建数组,复制,字节对齐....
}

6.2 Go1.18 的 growslice()

func growslice(et *_type, old slice, cap int) slice {// 检查...// 确定新 capnewcap := old.capdoublecap := newcap + newcapif cap > doublecap {// ① 如果新 cap 大于两倍旧 cap,则直接使用新 capnewcap = cap} else {const threshold = 256if old.cap < threshold {// ② 如果新 cap 小于两倍旧 cap 且旧 cap 小于 256,则 cap 直接翻倍newcap = doublecap} else {// ③ 如果新 cap 小于两倍旧 cap 且旧 cap 大于 256,则增加幅度逐渐从 2x 降到 1.25x// 原始容量  			扩容系数//	 256					2.0//	 512          1.63//   1024         1.44//   2048         1.35//   4096         1.30for 0 < newcap && newcap < cap {newcap += (newcap + 3*threshold) / 4}...}}// 新建数组,复制,字节对齐....
}

总结

  • Go1.17 及以前
    • 如果 newcap 大于 2*oldcap,则直接使用 newcap
    • 否则
      • 如果 oldcap < 1024,则 2*oldcap
      • 如果 oldcap >= 1024,则 1.25*oldcap
  • Go1.18
    • 如果 newcap 大于 2*oldcap,则直接使用 newcap
    • 否则
      • 如果 oldcap < 256,则 2*oldcap
      • 如果 oldcap >= 256,则 oldcap += (oldcap + 3*256) / 4

在 Go1.18 中,优化了切片在容量较大时扩容的策略,让底层数组大小的增长更加平滑:通过减小阈值并固定增加一个常数,使得优化后的扩容的系数在阈值前后不再会出现从 2 到 1.25 的突变。该 commit 作者给出了几种原始容量下对应的“扩容系数”:

原始容量扩容系数
2562.0
5121.63
10241.44
20481.35
40961.30

PS:slice 在扩容的时候是并发不安全的,在并发访问的时候,需要加锁。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com