Slice扩容相关知识

1 篇文章 0 订阅
1 篇文章 0 订阅

对于数组的优点

  • 在 Go 中,与 C 数组变量隐式作为指针使用不同,Go 数组是值类型,赋值和函数传参操作都会复制整个数组数据。
  • 但是把第一个大数组传递给函数会消耗很多内存,采用切片的方式传参可以避免上述问题。
  • 切片是引用传递,所以它们不需要使用额外的内存并且比使用数组更有效率。

底层结构

切片是对数组一个连续片段的引用,所以切片是一个引用类型。

当创建切片的时候,发现大小只为24,原因就是他本质是一个结构体,存放着3个字段


    type slice struct {
      // 数组指针
    	array unsafe.Pointer
      // 长度
    	len   int
      // 容量
    	cap   int
    }

nil切片 和 空切片

  • nil 切片的指针指向 nil。
  • 空切片指向的地址不是nil,指向的是一个内存地址,但是它没有分配任何内存空间,即底层元素包含0个元素。
  • 不管是使用 nil 切片还是空切片,对其调用内置函数 append,len 和 cap 的效果都是一样的。

扩容原理

在这里插入图片描述

扩容策略

  • 首先判断,如果新申请容量大于2倍的旧容量,最终容量就是新申请的容量;
  • 否则判断,如果旧切片的长度小于1024,则最终容量就是旧容量的两倍;
  • 否则判断,如果旧切片长度大于等于1024,则最终容量从旧容量开始循环增加原来的 1/4,直到最终容量大于等于新申请的容量。
  • 如果最终容量计算值溢出,则最终容量就是新申请容量

扩容策略的小问题

第一个例子

    func main() {
        // 新建一个切片
        s := []int{5}
    
        // 给s中添加7和9
        s = append(s,7)
        s = append(s,9)
    
        // 给s中添加11,赋给x
        x := append(s,11)
        // 给y中添加12,赋给y
        y := append(s,12)
    
        // 输出
        fmt.Println(s,x,y)
    }

输出结果:

5,7,9 5,7,9,12

第二个例子

 func main(){
      // 新建一个切片
      s := []int{5,7,9}
    
      // 给s中添加11,赋给x
      x := append(s,11)
      // 给y中添加12,赋给y
      y := append(s,12)
    
      // 输出
      fmt.Println(s,x,y)
    }

输出结果:

5,7,9 [5,7,9,12]

第一种情况:

  1. 创建s时,cap1和len1内存中数据为[5],s指针假设为p1。
  2. append(s,7):按slice扩容机制cps(s)翻倍 cps=2新建一个新的底层数组,此时s指针指向新的数组p2,底层数组元素[5,7]。
  3. appen(s,9):按slice扩容机制 cps(s)翻倍 cps=4 新建一个新的底层数组 将s指向的数组 复制到newarr 然后将 9 加到newarr 此时 s指向新数组 p3 数组元素为[5,7,9]
  4. x:= append(s,11):此时,容量足够不需要扩容,底层数组数据为[5,7,9,11],此时x和s共同指向这个底层数组p3
  5. y:= append(s,12): 此时cps(s)=4容量足够不需要扩容,底层数组第四个元素 用12覆盖之前的11,s和y共同指向这个底层数组数组元素为[5,7,9,12]

s, x, y 都指向了同一个内存地址,所以添加12时会将之前添加的11覆盖。

第二种情况:

  1. s := []int{5,7,9},cap(s) = 3
  2. x := append(s,11)需要扩容,新建一个底层数组,x指向这个内存地址
  3. y := append(s,12)需要扩容,新建一个底层数组,y指向这个内存地址

所以 x,y 指向不同的数组,自然值也就不一样了。

新数组 or 老数组

一般情况下,扩容会产生新的数组,切片中的指针会指向这个新的数组。

但是有一种情况不会产生新的数组:

 package main
    
    import "fmt"
    
    func main() {
    	a := [4]int{10, 20, 30, 40}
    	s1 := a[0:3]
    	s2 := a[0:2]
    
    	fmt.Printf("s1= %v\tlen = %d, cap = %d\n", s1, len(s1), cap(s1))
    	fmt.Printf("s2= %v\tlen = %d, cap = %d\n", s2, len(s2), cap(s2))
    
        println("在s2中新增元素,赋给s3")
    	s3 := append(s2, 50)
    
    	fmt.Printf("s1= %v\tlen = %d, cap = %d\n", s1, len(s1), cap(s1))
    	fmt.Printf("s2= %v\tlen = %d, cap = %d\n", s2, len(s2), cap(s2))
    	fmt.Printf("s3= %v\tlen = %d, cap = %d\n", s3, len(s3), cap(s3))
    	println()
    
        println("修改s3元素")
    	s3[1] += 10
    
    	fmt.Printf("s1= %v\tlen = %d, cap = %d\n", s1, len(s1), cap(s1))
    	fmt.Printf("s2= %v\tlen = %d, cap = %d\n", s2, len(s2), cap(s2))
    	fmt.Printf("s3= %v\tlen = %d, cap = %d\n", s3, len(s3), cap(s3))
    	println()
    
    }

测试结果
在这里插入图片描述

总结:

通过数组/切片截取出的切片,并且没有规定容量或规定的容量不等于切片的长度,并且扩容后的大小小于原数组/切片的大小。

在这种情况下,扩容以后并没有新建一个新的数组,扩容前后的数组都是同一个,这也就导致了新的切片修改或新增了一个值,也影响到了原来的切片/数组和之前定义的切片了。

由于原数组还有容量可以扩容,所以执行 append() 操作以后,会在原数组上直接操作,即直接覆盖数组上下一个元素。

参考文章:https://blog.csdn.net/guzarish/article/details/118626698

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值