当前位置: 首页 > news >正文

Golang 切片(深入了解切片底层扩容机制,部分源码,测试实战+核心用法)

大家如果又不喜欢文字版的,作者有对应的视频版讲解切片,超级详细,带大家精准定位golang切片必须掌握的重点内容,欢迎大家观看!!!下方链接直通视频版

别再被Golang 切片坑了! 底层源码 + 测试实战 + 核心用法(大学生精讲计算机知识,不看会后悔系列!)_哔哩哔哩_bilibili

一、来由

先有固定数组 ➡ 后有可变切片 ➡  切片是对数组的封装与扩展 

总结:数组提供底层连续内存,切片提供动态长度+共享视图,二者分工协作,成就了Go语言高效又易用的序列容器

二、定义

   切片又称动态数组,依托数组实现,可以方便地进行扩容和传递,实际使用时比数组更灵活,正因为灵活,实际使用时容易出错,避免出错的方法之一便是了解其使用原理

三、操作语法

1. 初始化

声明和初始化切片的方式主要有以下几种:

 1)变量声明;

这种方式声明的切片变量与声明其他类型变量一样,变量值都为零值,对于切片来讲零值为 nil

 2)字面量;

  字面量初始化切片,需要知道的是空切片是指长度为空,其值并不是 nil,声明长度为 0 的切片时,推荐使用变量声明的方式获得一个 nil 切片,而不是空切片,因为 nil 切片不需要分配内存

 3)使用内置函数 make();

内置函数 make()可以创建切片,切片元素均初始化为相应类型的零值

推荐使用指定长度的同时指定预估空间,可以有效地减少切片扩容时内存分配及拷贝次数

 4)从切片和数组中切取。

      切片可以基于数组和切片创建,需要了解的是切片与原数组或切片共享底层空间吗,修改切片会影响原数组或切片      

      切片表达式 [low : high] 表示的是左闭右开 [low:high) 区间,切取的长度为 hight - low。

2. 内置函数 append()用于向切片中追加元素

当切片空间不足时,append()会先创建新的大容量切片,添加元素之后再返回新切片

四、实现原理

切片依托于数组实现,底层数组对用户屏蔽,在底层数组容量不足时,可以实现自动分配并生成新的切片,接下来会根据实际使用场景来分别介绍其机制

1. 数据结构

源码包中 src/runtime/slice.go:slice 定义了 slice 的数据结构:

从数据结构上看 slice 很清晰,arrray 指针指向底层数组,len 表示切片长度,cap表示底层数组容量

2. 切片操作:使用 make() 创建slice

       使用make() 创建 slice 时,可以同时指定长度和容量,创建时底层会分配一个数组,数组的长度即为容量

      例如,slice := make( [ ] int,5,7) 语句创建的 slice 的结构如下图所示:

3. 使用数组创建 slice

使用数组创建 slice 使用数组创建 slice 时,slice将与原数组共用一部分内存

例如,slice := array[5:7] 语句所创建的 slice 的结构图如下

4. slice扩容       

       使用 append 向 slice追加元素时,如果 slice 空间不足,则会触发 slice 扩容,扩容实际上是重新分配一块更大的内存,将原 slice 的数据拷贝进新 slice,然后返回新 slice,扩容后再将数据追加进去。        

      例如,当向一个 capacity 为 5 且 length 也为 5 的 slice 再次追加 1 个元素时,就会发生扩容,如后图所示:

  扩容操作只关心容量,会把原 slice 的数据拷贝到新 slice 中,追加数据由 append 在扩容结束后完成,由上图可见,扩容后新的 slice 长度仍然是 5 ,但是容量由 5 提升到了 10,原 slice 的数据也都拷贝到了新 slice 指向的数组中

Go 1.18之前遵循的扩容基本规则:

    1. 如果原 slice 的容量小于 1024,则新 slice 的容量将扩大为原来的2倍;      

    2. 如果原 slice 的容量大于或等于 1024,则新的 slice 的容量将扩大为原来的 1.25 倍

优化之前的源码部分:

Go 1.18扩容机制优化版:

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

   1. 当新切片需要的容量cap大于两倍扩容的容量,则直接按照新切片需要的容量扩容;  

   2. 当原 slice 容量 < threshold 的时候,新 slice 容量变成原来的 2 倍;  

   3. 当原 slice 容量 > threshold,进入一个循环,每次容量增加

扩容机制优化说明:

在 Go 1.18中,优化了切片扩容的策略,让底层数组大小的增长更加平滑: 通过减小阈值并固定增加一个常数,使得优化后的扩容的系数在阈值前后不再会出现从2到1.25的突变

原始容量对应的扩容系数:

   可以看到,Go1.18的扩容策略中,随着容量的增大,其扩容系数是越来越小的,可以更好地节省内存。

Go 1.18优化的扩容机制代码单元测试:

代码演示运行结果:

大家可以自己下去尝试一下!

五、切片表达式

 slice 表达式可以基于一个字符串(string)生成子字符串,也可以从一个数组或切片中生成切片,Go语言提供了两种 slice 表达式:

 1. 简单表达式:a [low  :high];       2. 扩展表达式:a [low  :   high  :  max] 。

1. 简单表达式,其格式为:

       如果 a 为数组或切片,则该表达式将切取 a 位于 [low,high] 区间的元素并生成一个新的切片,简单表达式生成的切片的长度为 high - low ,例如下面的例子:

边界问题:

默认值:

为了使用方便,简单表达式 a[low :high] 中的 low 和 high 都是可以省略的,low的默认值为 0,而 high 的默认值为表达式作用对象的长度

2. 扩展表达式

   Go 团队很早就关注到了这个风险,并且在 Go1.2 中就提供了一种可以限制新切片容量的表达式,即扩展表达式:

    注意:扩展表达式中的 max 用于限制新生成切片的容量,新切片的容量为 max - low

注意:          

      1. 扩展表达式中的a[low  :  high  :  max] 只有 low 是可以省略的,其默认值为0,      

      2. 如果缺失了 high 或 max则会产生编译错误

六、切片使用技巧及错误规避:

1. 预分配容量优化性能:

2. 误用共享底层数组:

3. 切片作为参数传递:

update:共享底层数组,所以原切片也会被修改,会打印 [0, 90, 2], 底层数据结构的指针,len,cap不变

add:  函数里面的 len = 4,cap不变,但是回到main函数的 len 依旧是 3,所以main函数的切片最终只会输出三个数

http://www.dtcms.com/a/473668.html

相关文章:

  • go语言结构体内存对齐
  • 爬虫+卷积神经网络项目实战解析——对图像狗的识别分类
  • golang读写锁
  • 怎么用ftp清空网站大庆seo推广
  • 云南网官方网站博客园和wordpress
  • MyBatis基本工作原理
  • 第16届深圳国际移动电子展AI生活主题将带来哪些新体验?
  • AI智能体赋能战略分析与制订之仿真:“主权AI” —— 是国家安全的“诺亚方舟”,还是创新生态的“孤岛”?
  • 公司手机网站建设wordpress页眉页脚
  • MySQL时间格式转换,时间数据混乱不堪如何彻底重构?
  • Docker 安装 Node.js
  • vscode 怎么运行 c++ 文件
  • 【基础算法】记忆化搜索
  • wordpress yum上海搜索引擎优化公司排名
  • c++类和对象(下)
  • 算法7.0
  • 【异常处理——下】
  • axios请求
  • 109、23种设计模式之迭代器模式(18/23)
  • 餐饮设计公司网站wordpress如何保存
  • 前端页面出现问题ResizeObserver loop completed with undelivered notifications.
  • 有声阅读网站如何建设邵阳学院研究生与学科建设处网站
  • AWS RDS Aurora MySQL高CPU使用率问题诊断与解决实战
  • 【Swift】LeetCode 11. 盛最多水的容器
  • 设计模式之 享元模式 Flyweight
  • 智械觉醒当AI开始思考“我是谁”
  • 商河 网站建设公司网站的具体的建设方案
  • 湖南省网站备案婚纱摄影网站应该如何做优化
  • pytest学习
  • seo网站建设厦门百度广告代理商查询