go语言基础|slice入门

slice

slice介绍

slice中文叫切片,是go官方提供的一个可变数组,是一个轻量级的数据结构,功能上和c++的vector,Java的ArrayList差不多。

slice和数组是有一些区别的,是为了弥补数组的一些不足而诞生的数据结构。最大的区别就是数组长度固定,不可扩容,而切片是可以扩容的。也就是这个功能的区别导致了后续一系列的区别,例如在传参上面,数组是整个数组的值复制过去传到函数里,而切片则是传递指针等。

Go 复制代码
 func change(arr *[3]int) {
     arr[0] = 1
 }
 func change1(arr [3]int) {
     arr[0] = 1
 }
 func TestName(t *testing.T) {
     arr := [3]int{1, 2, 3}
     arr[0] = 2
     change(&arr)
     //change1(arr) // 这两者的结果是不一样的
     fmt.Println(arr[0])
 }

那么slice是什么呢? slice结构体源码如下:(在runtime/slice.go中)

Go 复制代码
 type slice struct {
     array unsafe.Pointer // 指向底层数组的指针
     len   int            // 当前长度
     cap   int            // 总容量
 }

slice扩容机制

1.18之前的方式和现在不太一样,Go1.18之前切片的扩容是以容量1024为临界点,当旧容量 < 1024个元素,扩容变成2倍;当旧容量 > 1024个元素,那么会进入一个循环,每次增加25%直到大于期望容量。

Go 复制代码
 func TestSliceGrowing(t *testing.T) {
    s := []int{}
    for i := 0; i < 4098; i++ {
       s = append(s, i)
       t.Log(len(s), cap(s))
    }
 }
 ​
 作者:starine
 链接:https://juejin.cn/post/7101928883280150558
 来源:稀土掘金
 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Go1.18不再以1024为临界点,而是设定了一个值为256的threshold,以256为临界点;超过256,不再是每次扩容1/4,而是每次增加(旧容量+3*256)/4;

当新切片需要的容量大于两倍的旧容量时,则直接按照新切片需要的容量扩容; else: 当原 slice 容量 < threshold 的时候,新 slice 容量变成原来的 2 倍; 当原 slice 容量 > threshold,进入一个循环,每次容量增加(旧容量+3*threshold)/4。

作者:starine 链接:https://juejin.cn/post/7101928883280150558 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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   //这里设置了一个 阈值 -- 256
         if old.cap < threshold {        //如果旧容量 小于 256,则两倍扩容
             newcap = doublecap   
         } else {
         // 检查 0 < newcap 以检测溢出并防止无限循环。
             for 0 < newcap && newcap < cap {   //如果新容量 > 0  并且 原容量 小于 所需容量
         
                // 从小片的增长2x过渡到大片的增长1.25x。这个公式给出了两者之间的平滑过渡。(这里的系数会随着容量的大小发生变化,从2.0到无线接近1.25)
                 newcap += (newcap + 3*threshold) / 4
               
                 
               //当newcap计算溢出时,将newcap设置为请求的上限。
             if newcap <= 0 {   // 如果发生了溢出,将新容量设置为请求的容量大小
                 newcap = cap
             }
         }
     }
 }

具体情况如下:

如果请求容量 大于 两倍现有容量 ,则新容量 直接为请求容量

否则(请求容量 小于等于 两倍现有容量) 如果 现有容量 小于 256 ,则新容量是原来的两倍

否则:新容量 = 1.25 原容量 + 3/4 阈值

golang slice (切片) 扩容机制详解(1.18版本后) - 小星code - 博客园

这么设计的目的是为了扩容能平滑,更好地节省内存。

传参问题

slice被make出来就是一个结构体的实例。当作为一个参数传递到方法里,会传递一个这个slice实例的值过去,这也是导致slice传参会有一些列奇怪现象的原因(可以修改值但无法扩容等)。可以修改数值的原因是这个值中的array指针是指向原数组的,但是无法扩容的原因是修改这个值的指针对原slice是无影响的;而传递slice指针可以修改成功是因为本质是就是在修改原方法层面的slice,而不是修改传递后slice的值。

线程安全性问题

slice是线程不安全的数据结构,因此会存在竞态条件(race condition),处理原则要么只读,要么加锁

Slice 的底层是 数组指针 + len + cap,这几个在并发时候都可能出现竞态条件。

相关推荐
追逐时光者5 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_5 小时前
敏捷开发流程-精简版
前端·后端
苏打水com6 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
间彧7 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧7 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧7 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧7 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧7 小时前
Spring Cloud Gateway详解与应用实战
后端
EnCi Zheng9 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端
烙印6019 小时前
Spring容器的心脏:深度解析refresh()方法(上)
java·后端·spring