Java/Go双修 - Go切片Slice原理

Slice是什么

Slice也称为切片,切片其实就是建立在Go的数组之上的抽象类型,如果要理解切片,我们必须了解数组

对于Javaer来说,Slice就是Java的List,也就是动态数组

  • 为什么有数组还需要切片?

    数组长度在声明阶段已经固定,我们要想一个更大的数组,只能重新申请新数组,抛弃旧数组

    在应对动态数据集合处理问题的时候,显得捉襟见肘,比如从网络中读取数据等场景,难以定义一个合适大小的数组

Slice的底层剖析 - 扩容机制

go 复制代码
type slice struct {
	array unsafe.Pointer		// 底层数组指针(或者说是指向一块连续内存空间的起点)
	len   int								// 长度
	cap   int								// 容量
}
  • slice怎么做扩容的?

    1.如果新切片的长度 > 旧切片容量的两倍,则新切片容量就为新切片的长度

    2.如果旧切片的容量 < 256,那么新切片的容量就是旧切片的容量的两倍

    3.如果旧切片的容量 > 256,那么新切片的容量会按照1.25倍的增速,直到 >= 新切片的长度

go 复制代码
func growslice(et *_type, old slice, cap int) slice {
      newcap := old.cap
      doublecap := newcap + newcap
      if cap > doublecap {
        newcap = cap
      } else {
        const threshold = 256
        if old.cap < threshold {
          newcap = doublecap
        } else {
          for 0 < newcap && newcap < cap {
            newcap += (newcap + 3*threshold) / 4
          }
          if newcap <= 0 {
            newcap = cap
          }
        }
      }
}
  • slice的内存对齐

    切片的每一次扩容,都会去尝试进行内存对齐,举个例子,假设当前操作系统的内存对齐是8个字节

    那么每一次内存对齐就是8 16 24 32 48 64 80 ...,那么如果你的下一次扩容的容量为60,那么最终分配会分配64

    最终分配的mallogc的容量大小是 capmem,源码有点长,这里不贴出来了,感兴趣可以去 growslice() 看看

    ini 复制代码
    p = mallocgc(capmem, nil, false)

切片通过函数传递,传的是什么?

传递的是切片的三个值 unsafe.Pointer、len、cap,这三个值,传到一个新的slice,但是指针指向的同一个底层数组

go 复制代码
func main() {
	s := make([]int, 5, 10)
	PrintSliceStruct(&s)
	test0(s)
}

func test0(s []int) {
	PrintSliceStruct(&s)
}

func PrintSliceStruct(s *[]int) {
	// 代码 将slice 转换成 reflect.SliceHeader
	ss := (*reflect.SliceHeader)(unsafe.Pointer(s))
	// 查看slice的结构
	fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}

输出结果可以看到,底层数组的地址都是一致的

ini 复制代码
slice struct: &{Data:1374390779904 Len:5 Cap:10}, slice is &[0 0 0 0 0]
slice struct: &{Data:1374390779904 Len:5 Cap:10}, slice is &[0 0 0 0 0]

在函数里改变切片,函数外的切片会被影响吗?append()分析

这里需要分两种情况讨论,重点是切片的底层数组变不变

上面我们提到切片的传递是值传递,传递的是数组指针,既然都指向同一个内存地址,那里层函数修改肯定会导致外层修改的

这里就需要和Java的List做一个区分了,Java中的List在每次扩容的时候,都不会改变原数组指针的指向,Go则不同

  • 切片底层数组不变的情况
  • 切片底层数组变的情况
  • 代码理解
go 复制代码
func main() {
	s := make([]int, 5)
	PrintSliceStruct(&s)
	case1(s)
	case2(s)
}

// 底层数组不变
func case1(s []int) {
	s[1] = 1
	PrintSliceStruct(&s)
}

// 底层数组变化
func case2(s []int) {
	s = append(s, 0)
	s[1] = 1
	PrintSliceStruct(&s)
}

func PrintSliceStruct(s *[]int) {
	// 代码 将slice 转换成 reflect.SliceHeader
	ss := (*reflect.SliceHeader)(unsafe.Pointer(s))

	// 查看slice的结构
	fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}

输出结果

ini 复制代码
slice struct: &{Data:1374390755376 Len:5 Cap:5}, slice is &[0 0 0 0 0]
slice struct: &{Data:1374390755376 Len:5 Cap:5}, slice is &[0 1 0 0 0]
slice struct: &{Data:1374390837248 Len:6 Cap:10}, slice is &[0 1 0 0 0 0]

切片截取

go 复制代码
s := make([]int,5)
s1 = s[1:]	// 截取0号元素以后的元素
s2 = s[1:3]	// 截取[1,2]区间的元素 注意[1:3)左闭右开
s3 = s[:3]	// 相当于[0,3) 截取 [0,2]区间的元素

这种切片截取是怎么做的?其实是重新创建了一个切片,但是旧切片和新切片共享同一个底层数组,和append()也是一样的逻辑

  • 共享同一个底层数组

当新切片s2再进行append()的时候,此时就会触发扩容,并指向新的底层数组的地址,和上面的append()逻辑是一样的

代码题思考

输出结果是?

go 复制代码
func main() {
    doappend := func(s []int) {
        s = append(s, 1)
        printLenthAndCapacity(s)
    }
    s := make([]int, 8, 8)
    doappend(s[:4])
    printLenthAndCapacity(s)

    doappend(s)
    printLenthAndCapacity(s)
}

func printLenthAndCapacity(s []int) {
    fmt.Println(s)
    fmt.Printf("len=%d cap=%d \n", len(s), cap(s))
}
ini 复制代码
0 0 0 0 1, len 5, cap 8
0 0 0 0 1 0 0 0, len 8, cap 8
0 0 0 0 1 0 0 0 1, len 9, cap 16
0 0 0 0 1 0 0 0, len 8, cap 8
相关推荐
小江的记录本8 小时前
【JVM虚拟机】垃圾回收GC:四种引用类型:强引用、软引用、弱引用、虚引用(附《思维导图》+《面试高频考点清单》)
java·jvm·spring boot·后端·python·spring·面试
小马爱打代码8 小时前
Spring源码 第四篇:Spring 5 源码深度拆解:AOP 全流程核心原理
java·后端·spring
ServBay9 小时前
2026 Mac 本地大模型部署深度解析与混合架构指南
后端·macos·aigc
一拳一个娘娘腔9 小时前
【SRC漏洞挖掘系列】第10期:GraphQL & API 安全 —— 现代 API 的“裸奔”时代
后端·安全·graphql
ZhengEnCi10 小时前
01-如何监听接口调用情况?
java·spring boot·后端
小马爱打代码11 小时前
Spring源码 第九篇:Spring 5 源码深度拆解 - Spring 事件驱动模型
java·后端·spring
ForgeAI码匠12 小时前
ForgeAdmin|Spring Boot 3 后台框架的自动配置设计:少写配置,多做组合
java·spring boot·后端
IT_陈寒12 小时前
为什么 Java 的 Optional 让我调试到深夜?
前端·人工智能·后端
用户83562907805112 小时前
用 Python 实现 Excel 散点图绘制与定制
后端·python
怪兽陪你看日出B13 小时前
一文彻底搞懂本地缓存之王-Caffeine
后端