go语言Array 与 Slice

news2024/11/19 16:45:37

有的语言会把数组用作常用的基本的数据结构,比如 JavaScript,而 Golang 中的数组(Array),更倾向定位于一种底层的数据结构,记录的是一段连续的内存空间数据。但是在 Go 语言中平时直接用数组的时候不多,大多数场景下我们都会直接选用更加灵活的切片(Slice)

1 数组

声明与初始化

# 声明
var arr [5]int
var buffer [256]byte

# 初始化方式有两种,一种是显示声明长度,另一种是[...]T推断长度,会经过编译器推导,得到数组长度
arr1 := [3]int{0,1,2}
arr2 := [...]string{"Joey","Sophie"}

Go 为不同类型不同结构的初始化方式进行了优化(不止是数组的初始化这一点上,其它一些代码同样如此),对于优化过程,可以简单概括为下面的话:

  • 如果数组中元素的个数小于或者等于 4 个,那么所有的变量会直接在栈上初始化;
  • 如果数组元素大于 4 个,变量就会在静态存储区初始化然后拷贝到栈上,这些转换后的代码才会继续进入中间代码生成和机器码生成两个阶段,最后生成可以执行的二进制文件。

2 slice

区别 Slice 与 Array 的首要关键是记住下面几点:

  • Slice 不是 Array,它描述一个 Array
  • Slice 的本质是一个 Struct这是他长度可变的根本原因

Go 源码中找到 sliceHeader 的定义:

type sliceHeader struct {
    Data unsafe.Pointer // 指向的数组
    Len  int            // 长度,即 Slice 截取 Data 的长度
    Cap  int            // 容量,即 Data 的大小,显然不会小于 Len
}

Slice 的声明方式比较多,我们可以直接构建一个空 Slice 而不需要指定长度,我们也可以直接基于 Array 本身构建一个 Slice,亦可以基于 Slice 构建新的 Slice

var sli0 = make([]int) // make([]T, Len, Cap)
var sli1 = arr1[5:10]
var sli2 = sli1[2:]

sli1 在 arr1 的左闭右开索引区间 [5, 10) 上构建了切片,而 sli2 又在 sli1 的基础上构建了 [2, 5) 的切片,这里值得记住的一点是,切片结构体里保存的是底层数组的指针(引用),因此他们指向的是同一块底层数组

2.1 函数传递 Slice

切片作为函数参数直接传递时就是个普通的值传递,但是 Slice 这个值很特殊,他里面存有数组的指针,又包含了 Slice 的 Len 和数组的 Cap,即又包含指针又包含普通值,因此:

  • 直接传递 Slice 进函数时,传递的是 Slice 的 copy;
  • 对 Slice 的元素进行修改操作,会通过指针直接修改数组,因此是可以实现的;
  • 对 Slice 的长度修改,修改的是 copy 对象的 Len 字段,因此原 Slice 是长度是不会变的;
  • 想要在函数内修改 Slice 的长度,最好的方式是传递 Slice 的指针;

2.2 容量与 append

append 返回的是一个新的 slice,直接 append 而不赋值给原 slice 的话,原 slice 长度是不会改变的

names = append(names, "Joey")
# 移除逻辑
ages = append(ages[:5], ages[6:])

关于容量需要记住的就是:当向 Slice 追加元素导致 Len大于 Cap 时,会触发扩容机制,创建一个Cap大于原数组的新数组(首元素地址不一致),并将值拷贝进新数组,之后再改变Slice元素值时改变的是新创建的数组(切断与原数组的引用关系)。是的,当触发扩容机制后,新的 Slice 底层数组已经不再是之前的数组了,对于 Slice 元素的修改都是基于新的底层数组进行。
因此我们如果真的关注性能这一块儿的话,一定要想办法避免频繁的触发扩容机制,比如当我们明确地知道 Slice 容量上限的时候,在声明时就应该通过 make([]T, Len, Cap) 给出明确的 cap 值

2.3 slice扩容

go1.17 及以下版本扩容机制如下:

代码的扩容策略可以简述为以下三个规则:
1.当期望容量 > 两倍的旧容量时,直接使用期望容量作为新切片的容量
2.如果旧容量 < 1024(注意这里单位是元素个数),那么直接翻倍旧容量
3.如果旧容量 > 1024,那么会进入一个循环,每次增加25%直到大于期望容量
可以看到,原来的go对于切片扩容后的容量判断有一个明显的magic number:1024,在1024之前,增长的系数是2,而1024之后则变为1.25。

关于为什么会这么设计,社区的相关讨论1给出了几点理由:1.如果只选择翻倍的扩容策略,那么对于较大的切片来说,现有的方法可以更好的节省内存。2.如果只选择每次系数为1.25的扩容策略,那么对于较小的切片来说扩容会很低效。3.之所以选择一个小于2的系数,在扩容时被释放的内存块会在下一次扩容时更容易被重新利用

func growslice(et *_type, old slice, cap int) slice {
  ...
  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
      }
    }
  }
  ...
}

go1.18 及以上版本扩容机制如下
在这里插入图片描述

//1.18
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
  newcap = cap
} else {
  const threshold = 256
  if old.cap < threshold {
    newcap = doublecap
  } else {
    // Check 0 < newcap to detect overflow
    // and prevent an infinite loop.
    for 0 < newcap && newcap < cap {
      // Transition from growing 2x for small slices
      // to growing 1.25x for large slices. This formula
      // gives a smooth-ish transition between the two.
      newcap += (newcap + 3*threshold) / 4
    }
    // Set newcap to the requested cap when
    // the newcap calculation overflowed.
    if newcap <= 0 {
      newcap = cap
    }
  }
}

在这里插入图片描述
在1.18中,优化了切片扩容的策略2,让底层数组大小的增长更加平滑:通过减小阈值并固定增加一个常数,使得优化后的扩容的系数在阈值前后不再会出现从2到1.25的突变,该commit作者给出了几种原始容量下对应的“扩容系数”:
在这里插入图片描述

内存对齐,进一步调整newcaps
slice的扩容还与数据类型有关,当数据类型size为1字节,8字节,或者2的倍数时,会根据内存大小进行向上取整,之后返回新的扩容大小。
这是由于Go语言的内存管理模块返回给你需要的内存块,通常这些内存块都是预先申请好,并且被分为常用的规格,比如8,16, 32, 48, 64等。

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

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

相关文章

MySQL中查询重复字段的方法和步骤是怎样

示例 accountinfo 表数据如下&#xff1a; 场景一 单个字段重复数据查找 & 去重 我们要把上面这个表中 单个字段 account字段相同的数据找出来。 思路 分三步 简述&#xff1a; 第一步 要找出重复数据&#xff0c;我们首先想到的就是&#xff0c;既然是重复&#xff0c…

【斗破年番】再遭群嘲,美杜莎怀孕之事被魔改,三方联手除萧潇?

【侵权联系删除】【文/郑尔巴金】 斗破苍穹年番第67集已经更新了。和很多人一样&#xff0c;小郑也去看了&#xff0c;只是小郑万万没有想到&#xff0c;我满怀期待的去看这一集&#xff0c;这一集却能魔改成这样。魔改成什么样了呢&#xff1f;下面来分析下吧&#xff01; 一&…

高效表达三步

一、高效表达 高效表达定主题搭架子填素材 第一&#xff1a; 1个核心主题&#xff0c;让别人秒懂你的想法 &#xff08;表达要定主题&#xff09; 第二&#xff1a; 3种经典框架&#xff0c;帮你快速整理表达思路 第三&#xff1a; 2种表达素材&#xff0c;让发言更具说服力…

基础算法相关笔记

排序 最好情况下&#xff1a; 冒泡排序 最坏时间复杂度 O ( n 2 ) O(n^2) O(n2)。 插入排序 最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2)&#xff0c;最优时间复杂度为 O ( n ) O(n) O(n)。 平均情况下&#xff1a; 快速排序 最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2)&…

跟我一起写个虚拟机 .Net 7(四)- LC_3 解析实例

没想到这篇文章持续了这么久&#xff0c;越学越深&#xff0c;愣是又买了一本书《计算机系统概论》&#xff0c;当然&#xff0c;也看完了&#xff0c;受益匪浅。 系统化的学习才是正确的学习方式&#xff0c;我大学就没看到过这本书&#xff0c;如果早点看到&#xff0c;可能…

可视化 | python可视化相关库梳理(自用)| pandas | Matplotlib | Seaborn | Pyecharts | Plotly

文章目录 &#x1f4da;Plotly&#x1f407;堆叠柱状图&#x1f407;环形图&#x1f407;散点图&#x1f407;漏斗图&#x1f407;桑基图&#x1f407;金字塔图&#x1f407;气泡图&#x1f407;面积图⭐️快速作图工具&#xff1a;plotly.express&#x1f407;树形图&#x1f…

MySQL 排名函数 RANK, DENSE_RANK, ROW_NUMBER

文章目录 1 排名函数有哪些?2 SQL 代码实现2.1 RANK2.2 DENSE_RANK2.3 ROW_NUMBER 1 排名函数有哪些? RANK(): 并列跳跃排名, 并列即相同的值, 相同的值保留重复名次, 遇到下一个不同值时, 跳跃到总共的排名DENSE_RANK(): 并列连续排序, 并列即相同的值, 相同的值保留重复名…

图详解第六篇:多源最短路径--Floyd-Warshall算法(完结篇)

文章目录 多源最短路径--Floyd-Warshall算法1. 算法思想2. dist数组和pPath数组的变化3. 代码实现4. 测试观察5. 源码 前面的两篇文章我们学习了两个求解单源最短路径的算法——Dijkstra算法和Bellman-Ford算法 这两个算法都是用来求解图的单源最短路径的算法&#xff0c;区别在…

effective c++学习笔记(后四章)

六 继承与面向对象设计 红色字 \color{FF0000}{红色字} 红色字 32 确定你的public继承塑模出 is-a关系 如果你令class D (“Derived”)以public形式继承class B (“Base”)&#xff0c;你便是告诉C编译器&#xff08;以及你的代码读者&#xff09;说&#xff0c;每一个类型为…

基于目录的ant任务

一些任务利用目录树来执行一些动作 一些任务利用目录树来执行一些动作。例如&#xff0c;javac这个任务就是一个基于目录的任务&#xff0c;它将一个目录中的.java文件编译为.class文件。因为一些这样的任务在目录树上做很多的工作&#xff0c;所以这些任务本身充当了隐含的文…

C# Socket通信从入门到精通(2)——多个同步TCP客户端C#代码实现

前言: 我们在开发Tcp客户端程序的时候,有时候在同一个软件上我们要连接多个服务器,这时候我们开发的一个客户端就不够使用了,这时候就需要我们开发出来的软件要支持连接多个服务器,最好是数量没有限制,这样我们就能应对任意数量的服务器连接,由于我们开发的Tcp客户端程…

7个可能改变AEC行业的AI工具

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 人工智能&#xff08;AI&#xff09;工具在各个行业中越来越受欢迎&#xff0c;ChatGDP的推出无疑让人们看到了人工智能所能提供的可能性。 然而&#xff0c;人工智能不仅仅是生成文本或图形——它可以用于各种设置。 建筑…

【面试题】JDBC桥接模式如何实现的?

Hello 大家好&#xff0c;我是小米&#xff01;很高兴又和大家见面啦&#xff01;今天的主题是——"面试题&#xff1a;JDBC桥接模式如何实现的&#xff1f;"。 相信大家都听说过JDBC&#xff08;Java Database Connectivity&#xff09;&#xff0c;它是Java中连接…

QT判断平台和生成版本设置输入目录

QT判断平台和生成版本设置输入目录 pro工程文件中常用的宏定义Chapter1 QT判断平台和生成版本设置输入目录Chapter2 Qt pro文件中判断 x86/arm(aarch64)交叉编译环境&#xff0c;区分 linux/windows系统, debug/release版本Chapter3 Qt的版本判断、跨平台选择与pro工程文件输出…

231022|redis_demo

安装 https://github.com/tporadowski/redis https://github.com/redis/redis-py/ 解压后要先配置redis.windows.conf文件&#xff0c;里面有本地端口和密码设置 默认host:127.0.0.1 port:6379 打开命令行到redis文件夹下&#xff0c;redis-server.exe redis.windows.conf输入即…

1024我来利用DOS攻击你的电脑了?(第十三课)

1024我来利用DOS攻击你的电脑了&#xff1f;(第十三课) 本文章设计安全领域的重点问题 学习本文章时 请扎在初学者的角度学习 用于正途 一 国家安全法 1 安全法律法规 《宪法》中的相关规定 案例&#xff1a; 大山破解同事小美私人邮箱密码&#xff0c;读取其往来邮件 邮箱…

Go并发编程之四

一、前言 今天我们介绍一下Go并发编程另外一个重要概念【多路复用】&#xff0c;多路复用最开始是在网络通讯领域&#xff08;硬件&#xff09;应用&#xff0c;指的是用同一条线路承载多路信号进行通信的方式&#xff0c;有频分多路复用、时分多路复用等等技术&#xff0c;然…

组合数(递推版)的初始化

初始考虑为将第一列数和斜对角线上的数进行初始化。 橙色方块由两个绿色方块相加而来&#xff0c;一个为1&#xff0c;一个为0&#xff0c;所以斜对角线都为1&#xff0c;可以通过计算得来&#xff0c;不需要初始化&#xff0c;需要与码蹄集盒子与球 第二类Stirling数&#xf…

【Linux】命令行参数和环境变量

命令行参数 其实main函数是可以传参数的&#xff0c;也叫做命令行参数。我们这里先介绍main函数的前两个参数 argc代表的是指针数组的元素个数&#xff0c;argv是一个指针数组&#xff0c;指针指向字符串。argv不可能为空&#xff0c;argv【0】存储该进程的名字 例如 ls -a -…