Go 语言切片扩容规则是扩容2倍?1.25倍?到底几倍

本次主要来聊聊关于切片的扩容是如何扩的,还请大佬们不吝赐教

切片,相信大家用了 Go 语言那么久这这种数据类型并不陌生,但是平日里聊到关于切片是如何扩容的,很多人可能会张口就来,切片扩容的时候,如果老切片的容量小于 1024 那么就再扩容 1倍,也就是新的切片容量是老切片容量的两倍,同理,如果老切片容量大于 1024,那么就扩容1.25 倍

一个人这么说,多个人这么说,你可能就信了😂😂,可是大家都这么认为,我们就应该盲从吗?还是要自己去确认真实的扩容逻辑和实现方式,那就开始吧😁

结论先行,切片对于扩容并不一定是 2 倍,1.25倍,这个要看实际情况

本文分别从如下几点来聊聊切片的扩容

  • 扩容是针对切片的,数组无法扩容
  • 切片扩容到底是扩容到原来的几倍?
  • 我们一般使用切片的时候可以如何避免频繁的扩容?

扩容是针对切片的,数组无法扩容

首先需要明确,数组是不能扩容的,数组定义的时候就已经是定长的了,无法扩容

切片是可以扩容的,我们可以通过 append 追加的方式来向已有的切片尾部进行追加,若原有切片已满,那么就会发生扩容

另外,我们知道数组是一段连续的内存地址,同一种数据类型的数据集合,例如这样

func main() {log.SetFlags(log.Lshortfile)var demoArray = [5]int{1, 2, 3, 4, 5}log.Print("unsafe.sizeof(int) == ",unsafe.Sizeof(demoArray[0]))for i, _ := range demoArray {log.Printf("&demoAraay[%d] == %p", i, &demoArray[i])}}

可以看到在这个案例的环境中,一个 int 类型的变量占用 8 个字节,自然对于 demoArray 数组中,地址是连续的,每一个元素占用的空间也是我们所期望的

那么切片的数据地址也是连续的吗??

如果有人问这个问题,实际上是想问切片的底层数组的地址是不是也是连续的

我们知道,切片 slice 在 Go 中是一个结构体,其中 array 字段是一个指针,指向了一块连续的内存地址,也就是底层数组

type slice struct {array unsafe.Pointerlen   intcap   int
}

其中 len 字段记录了当前底层数组的实际有的元素个数,cap 表示底层数组的容量,自然也是切片slice 的容量

func main(){var demoSli = []int{1,2,3,4,5}log.Printf("len == %d,cap == %d",len(demoSli),cap(demoSli))for i, _ := range demoSli {log.Printf("&demoSli[%d] == %p", i, &demoSli[i])}
}

自然,demoSli 中的元素打印出来,地址也是连续的,没有毛病

此处 xdm 模拟的时候,切勿去打印拷贝值的地址,例如下面这种方式是相当不明智的

现在简单的去给 切片追加一个元素

可以看到切片的容量变成了原来的两倍(容量从 5 扩容成 10),且切片中底层数组的元素地址自然也是连续的,不需要着急下结论,继续往下看,好戏在后头

切片扩容到底是扩容到原来的几倍?

案例1 向一个cap 为 0 的切片中追加 2000 个元素,查看被扩容了几次

总共是扩容了 14 次

可以看到切片容量小于 1024 时,触发扩容都是扩容到原来的 2 倍,但是 大于 1024 之后,有的是 1.25 倍,有的是 1.35 倍,有的大于 1.35 倍,那么这是为什么呢?后面统一看源码

案例2 再次验证切片容量小于 1024,触发到扩容就一定是扩容 2 倍吗

  • 先初始化一个切片,里面有 5 个元素,len 为 5,cap 为 5
  • 再向切片中追加 6 个元素,分别是 6,7,8,9,10,11
  • 最终查看切片的容量是多少
func main(){var demoSli = []int{1, 2, 3, 4, 5}log.Printf("len == %d,cap == %d", len(demoSli), cap(demoSli))for i, _ := range demoSli {log.Printf("&demoSli[%d] == %p", i, &demoSli[i])}demoSli = append(demoSli,6,7,8,9,10,11)log.Printf("len == %d,cap == %d",len(demoSli),cap(demoSli))for i, _ := range demoSli {log.Printf("&demoSli[%d] == %p", i, &demoSli[i])}
}

通过这一段代码,我们可以看到,讲一个 len 为 5,cap 为 5 的切片,追加数字 6 的时候,切片应该要扩容到 10,然后追加到数字 11 的时候,切片应该扩容到 20,可实际真的是这样吗?

xdm 可以将上述 demo 贴到自己环境试试,得到的结果仍然会是切片的容量 cap 最终是 12,并不是 20

那么这一切都是为什么呢?我们来查看源码一探究竟

源码赏析

查看公共库中 runtime/slice.gogrowslice 函数就可以解开我们的疑惑

可以看出在我们使用 append 对切片追加元素的时候,实际上会调用到 growslice 函数, growslice 中的核心逻辑我们就可以理解为计算基本的 newcap 和进行字节对齐

  1. 进行基本的新切片容量计算
// 省略部分
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {newcap = cap
} else {if old.cap < 1024 {newcap = doublecap} else {// Check 0 < newcap to detect overflow// and prevent an infinite loop.for 0 < newcap && newcap < cap {newcap += newcap / 4}// Set newcap to the requested cap when// the newcap calculation overflowed.if newcap <= 0 {newcap = cap}}
}
// 省略部分

此处逻辑可以知道

  • 如果当前传入的 cap 是比原有切片 cap 的 2 倍还要大,那么就会按照当前传入的 cap 来作为新切片的容量
  • 否则去校验原有切片的容量是否小于 1024

    • 若小于 1024 ,则按照原有的切片容量的 2 倍进行扩容
    • 若大于等于 1024 ,那么就按照原有切片的 1.25 倍继续扩容

然后是否看到这里就就结束了呢?就下定论来呢?并不,我们切莫断章取义,需要看全整个流程

  1. 进行基本的字节对齐

growslice 函数 计算出基本的 newcap 之后,还需要按照类型进行基本的字节对齐,此处字节对齐之后主要是 roundupsize 的函数实现,顺便将其涉及到的常量放到一起给大家展示一波

const (_MaxSmallSize = 32768smallSizeDiv = 8smallSizeMax = 1024largeSizeDiv = 128_NumSizeClasses = 68_PageShift = 13
)
func roundupsize(size uintptr) uintptr {if size < _MaxSmallSize {if size <= smallSizeMax-8 {return uintptr(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]])} else {return uintptr(class_to_size[size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]])}}if size+ _PageSize < size {return size}return alignUp(size, _PageSize)
}func divRoundUp(n, a uintptr) uintptr {// a is generally a power of two. This will get inlined and// the compiler will optimize the division.return (n + a - 1) / a
}
var size_to_class8 = [smallSizeMax/smallSizeDiv + 1]uint8{0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, ...}

光看这个函数,没啥感觉,函数逻辑比较简单,就是基本的计算和索引,那么我们讲上述的案例2带入,来计算一下

此处很明确,当前旧的切片的 cap 为 5

也就是 growslice 函数 中 old.cap 为 5,传入的 cap 为 11,因此 cap > 2*old.cap

因此 newcap 此处等于 11

开始计算字节对齐之后的结果

  • roundupsize(uintptr(newcap) * sys.PtrSize) ,其中 newcap = 11,sys.PtrSize = 8,则 roundupsize 参数传入 88 ,此环境指针占用 8 字节
  • 按照如下逻辑进行计算

    • divRoundUp(88, 8) = 11
    • size_to_class8[11] = 8
    • class_to_size[8] = 96

此处环境我们的 int 类型是占用 8 个字节,因此最终的 newcap = 96/8 = 12

经过上述源码的处理,最终我们就可以正常的得到最终切片容量被扩容到 12 ,xdm 可以去看实际的源码

小结

使用 append 进行切片扩容的时候,先会按照基本的逻辑来计算 newcap 的大小

  • 如果当前传入的cap是比原有切片cap的2倍还要大,那么就会按照当前传入的cap来作为新切片的容量,否则去校验原有切片的容量是否小于 1024

  • 若小于1024,则按照原有的切片容量的2倍进行扩容

  • 若大于等于 1024,那么就按照原有切片的 1.25 倍继续扩容
    最终再进行字节对齐

那么实际上,最终的切片容量一般是会等于或者大于原有的 2倍 或者是 1.25 倍的

我们一般使用切片的时候可以如何避免频繁的扩容?

一般在使用切片的时候,尽量避免频繁的去扩容,我们可以对已知数据量的数据,进行一次性去分配切片的容量

例如,数据量有 1000 个,那么我们就可以使用 make 的方式来进行初始化

sli := make([]int, 0, 1000)

本次就是这样,如果对源码还挺感兴趣的话,xdm 可以去实际查看一下源码哦,希望对你有帮助

感谢阅读,欢迎交流,点个赞,关注一波 再走吧

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是阿兵云原生,欢迎点赞关注收藏,下次见~

文中提到的技术点,感兴趣的可以查看这些文章:

  • 你以为传切片就是传引用了吗?
  • 【切片】基础不扎实引发的问题
  • Go 语言中 panic 和 recover 搭配使用
  • Go 语言中的反射
  • 你真的知道 GO 中 nil 代表什么吗?
    可以进入地址进行体验和学习:https://xxetb.xet.tech/s/3lucCI

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/101235.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

unity中绑定动画的行为系统

主要代码逻辑是创建一个action队列,当动画播放结束时就移除队头,执行后面的事件 public class Enemy : MonoBehaviour {public event Action E_AnimatorFin;//当动画播放完毕时public Action DefaultAction;//默认事件public Dictionary<Action, string> EventAnimator n…

基于 chinese-roberta-wwm-ext 微调训练中文命名实体识别任务

一、模型和数据集介绍 1.1 预训练模型 chinese-roberta-wwm-ext 是基于 RoBERTa 架构下开发&#xff0c;其中 wwm 代表 Whole Word Masking&#xff0c;即对整个词进行掩码处理&#xff0c;通过这种方式&#xff0c;模型能够更好地理解上下文和语义关联&#xff0c;提高中文文…

振弦传感器和振弦采集仪应用隧道安全监测的解决方案

振弦传感器和振弦采集仪应用隧道安全监测的解决方案 现代隧道越来越复杂&#xff0c;对于隧道安全的监测也变得越来越重要。振弦传感器和振弦采集仪已经成为了一种广泛应用的技术&#xff0c;用于隧道结构的监测和评估。它们可以提供更精确的测量结果&#xff0c;并且可以在实…

0基础学习VR全景平台篇 第104篇:720全景后期软件安装

上课&#xff01;全体起立~ 大家好&#xff0c;欢迎观看蛙色官方系列全景摄影课程&#xff01; 摄影进入数码时代&#xff0c;后期软件继承“暗房工艺”&#xff0c;成为摄影师表达内在情感的必备工具。 首先说明&#xff0c;全景摄影与平面摄影的一个显著的区别是全景图片需…

ChatGPT私有数据结合有什么效果?它难吗?

ChatGPT的出现可谓是惊艳了全世界&#xff0c;ChatGPT的问答能力通过了图灵测试&#xff0c;使其回答问题的方式与人类几乎无法区分。大家不甘于只在官方的对话页面问答&#xff0c;想利用 GPT 模型的自然语言能力结合私有数据开拓更多的应用场景。 | ChatGPT私有数据结合特点 …

滚珠螺母在工业机器人中的应用优势

工业机器人是广泛用于工业领域的多关节机械手或多自由度的机器装置&#xff0c;具有一定的自动性&#xff0c;可依靠自身的动力能源和控制能力实现各种工业加工制造功能。滚珠螺母作为工业机器人中的重要传动配件&#xff0c;在工业机器人的应用中有哪些优势呢&#xff1f; 1、…

SpringMVC修炼之旅(1)什么是SpringMVC

一、什么是MVC 1.1概述 MVC是模型(Model)、视图(View)、控制器(Controller)的简写&#xff0c;是一种软件设计规范。 是将业务逻辑、数据、显示分离的方法来组织代码。 MVC主要作用是降低了视图与业务逻辑间的双向偶合。 MVC不是一种设计模式&#xff0c;MVC是一种架构模式…

主从Reactor高并发服务器

文章目录 Reactor模型的典型分类单Reactor单线程单Reactor多线程多Reactor多线程本项目中实现的主从Reactor One Thread One Loop各模型的优点与缺点 项目分解Reactor服务器模块BufferSocketChannelEpollerTimerWheelEventLoopAnyConnectionAcceptorLoopThreadLoopThreadPoolTc…

【大数据】hadoop安装部署(学习笔记)

一、集群组成概述 Hadoop集群包括两个集群&#xff1a;HDFS集群、YARN集群 两个集群逻辑上分离、通常物理上在一起 两个集群都是标准的主从架构集群 HDFS集群&#xff08;分布式存储&#xff09;&#xff1a; 主角色&#xff1a;NameNode从角色&#xff1a;DataNode主角色…

基于安卓android微信小程序的旅游系统

项目介绍 随着人民生活水平的提高,旅游业已经越来越大众化,而旅游业的核心是信息,不论是对旅游管理部门、对旅游企业,或是对旅游者而言,有效的获取旅游信息,都显得特别重要.自助定制游将使旅游相关信息管理工作规范化、信息化、程序化,提供旅游景点、旅游线路,旅游新闻等服务本…

Table.Group系列_第4参数为全局的情况下,利用第5参数进行分组汇总

原始数据: 部门与职位存在于同一列中 实现功能: 根据筛选条件,可对部门或职位进行统计汇总第一列列名根据筛选自动变更,显示当前统计的维度 实现方式: 1. 构建筛选器内容 在任意空白单元格内输入需要筛选的内容 2. 插入"组合框"控件,并进行相应设置 从开发工具…

麒麟系统加密/麒麟系统防泄密

​深信达网络科技有限公司自主研发的深信达主机加固系统软件V2.0、深信达沙盒防泄密系统软件V5.0&#xff0c;与麒麟软件完成兼容认证&#xff0c;并被纳入麒麟软件安全生态联盟成员之一。 麒麟软件主要面向通用和专用领域打造安全创新操作系统产品和相应解决方案&#xff0c;以…

大型语言模型:DistilBERT — 更小、更快、更便宜、更轻

一、介绍 近年来&#xff0c;大型语言模型的演进速度飞速发展。BERT成为最流行和最有效的模型之一&#xff0c;可以高精度地解决各种NLP任务。在BERT之后&#xff0c;一组其他模型随后出现在现场&#xff0c;也展示了出色的结果。 很容易观察到的明显趋势是&#xff0c;随着时间…

推荐开源工具带带弟弟ocr_ddddocr_各种验证码都可以识别_滑动_点击_等等---验证码识别工作笔记001

这个很强大了,常见的各种验证码都可以识别,如果你项目上也有需要,比如需要实现系统的自动登录,这个时候就很有用了,这里仅仅给出方案,具体如何用,用的时候在做研究吧,好东西要记录,分享给需要的人.使用的时候 自己去查一下如何使用非常简单. 支持点击的验证码,支持数字验证码就…

【C++】哈希

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a; C学习 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对我最大…

rust cfg的使用

前提是一个crate倒入另一个crate。 先看结构 test_lib目录结构 这与另一个crate处于同一个目录,所以另一crate倒入的时候在Cargo.toml中使用如下语句。 test_lib = {path = "../test_lib" }先在test_lib/src/abc/abc.rs中添加没有cfg的两个函数做测试。 pub fn…

ViewModifier/视图修饰符, ButtonStyle/按钮样式 的使用

1. ViewModifier 视图修饰符 1.1 创建默认按钮视图修饰符 ViewModifierBootcamp.swift import SwiftUI/// 默认按钮修饰符 struct DefaultButtonViewModifier: ViewModifier{let bcakgroundColor: Colorfunc body(content: Content) -> some View {content.foregroundColor…

C/C++之自定义类型(结构体,位段,联合体,枚举)详解

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂。 目录 个人主页&#xff1a;点我进入主页 …

MySQL 安装+启动+报错的解决方案

目录 一、安装准备 1.1 下载 1.2 版本说明 二、安装步骤 2.1 解压缩 2.2 配置环境变量 2.3 配置文件 2.4 安装 2.5 启动/停止服务 三、使用说明 3.1 用户名密码登录 3.1 设置用户名密码 四、卸载步骤 4.1 卸载服务 五、安装问题 六、启动问题 6.1 提示【服务无…

web基础和http协议

1.DNS解析的三种方式 DNS解析&#xff1a; 网站都是域名&#xff1a;dns解析的作用是把域名解析成ip地址 迭代&#xff1a;从跟域名到二级域 返回用户的过程&#xff1a;递归---运营商--本地hosts---用户 三种方式&#xff1a; /etc/hosts 本地解析&#xff0c;速度最快&…