go语言Array 与 Slice

有的语言会把数组用作常用的基本的数据结构,比如 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.capdoublecap := newcap + newcapif 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 = 256if 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.rhkb.cn/news/166688.html

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

相关文章

【Lua语法】字符串

Lua语言中的字符串是不可变值。不能像在C语言中那样直接改变某个字符串中的某个字符&#xff0c;但是可以通过创建一个新字符串的方式来达到修改的目的 print(add2(1 , 2 ,15,3))a "no one"b string.gsub(a , "no" , "on1111")print(a) print…

微软正式发布开源应用平台 Radius平台

“ 10 月 18 日&#xff0c;微软 Azure 孵化团队正式发布开源应用平台 Radius&#xff0c;该平台将应用程序置于每个开发阶段的中心&#xff0c;重新定义应用程序的构建、管理与理解方式。” 简单的概括就是&#xff0c;它和Kubernetes不一样&#xff0c;Radius将应用程序放在每…

C语言--程序环境和预处理

前言 本章就是c语言的最后一个板块了&#xff0c;学完这章节&#xff0c;我们将知道写出的代码如何变成可执行程序的&#xff0c;这是非常重要的一个章节&#xff0c;那让我们一起进入本章的学习吧。 本章重点&#xff1a; 程序的翻译环境程序的执行环境详解&#xff1a;C语言程…

周立功ZCANPRO简介和使用

ZCANPRO目录 周立功ZCANPRO简介一、软件安装ZCANPRO官网链接&#xff1a;驱动官网链接 二、ZCANPRO使用1.设备管理2.选择CAN、CANFD波特率计算器使用方法&#xff08;可选&#xff09; 3.新建视图CAN视图DBC视图 4.发送数据普通发送DBC发送 三、高级功能UDS诊断 周立功ZCANPRO简…

【java爬虫】使用selenium获取某交易所公司半年报数据

引言 上市公司的财报数据一般都会进行公开&#xff0c;我们可以在某交易所的官方网站上查看这些数据&#xff0c;由于数据很多&#xff0c;如果只是手动收集的话可能会比较耗时耗力&#xff0c;我们可以采用爬虫的方法进行数据的获取。 本文就介绍采用selenium框架进行公司财…

HTML选项框的设计以及根据不同选项的值对应不同的事件

文章目录 HTML选项框的设计JS根据不同的选项框对应出不同的事件 HTML选项框的设计 在前端页面的设计中&#xff0c;多选框的设计用select标签完成实现 全部选项都显示的选项框 <form><select multiple"multiple"><option></option><opti…

视频怎么压缩?视频过大这样压缩变小

在日常生活中&#xff0c;我们常常会遇到需要压缩视频的情况&#xff0c;视频压缩不仅可以减小文件大小&#xff0c;方便存储和传输&#xff0c;还可以在保证质量的同时&#xff0c;满足不同的使用需求。那么&#xff0c;如何有效地压缩视频呢&#xff1f; 方法一&#xff1a;嗨…

web APIs——第一天(上)

变量声明的时候建议 const优先&#xff0c;尽量使用const 原因&#xff1a; const语义化更好很多变量我们声明的时候就知道他不会被更改了&#xff0c;那为什么不用const呢&#xff1f;实际开发中也是&#xff0c;比如react框架&#xff0c;基本const如果你有纠结的时候&…

python中的yolov5结合PyQt5,使用QT designer设计界面没正确启动的解决方法

python中的yolov5结合PyQt5&#xff0c;使用QT designer设计界面没正确启动的解决方法 一、窗体设计test: 默认你已经设计好了窗体后&#xff1a; 这时你需要的是保存生成的untitle.ui到某个文件夹下&#xff0c;然后在命令行中奖.ui转换为.py&#xff08;&#xff0c;通过​​…

css之Flex弹性布局

文章目录 &#x1f415;前言&#xff1a;&#x1f3e8;定义flex容器 display:flex&#x1f3e8;在flex容器中子组件进行排列&#x1fa82;行排列 flex-direction: row&#x1fa82;将行排列进行翻转排列 flex-direction: row-reverse&#x1f3c5;按列排列 flex-direction: col…

2020年亚太杯APMCM数学建模大赛B题美国总统的经济影响分析求解全过程文档及程序

2020年亚太杯APMCM数学建模大赛 B题 美国总统的经济影响分析 原题再现&#xff1a; 美国总统选举每四年举行一次。 2020年是美国总统大选年&#xff0c;共和党候选人唐纳德特朗普和民主党对手乔拜登竞选总统。 甲乙双方候选人在金融贸易&#xff0c;经济金融治理&#xff0c;…

离散低通滤波方法

低通滤波器允许低频信号通过&#xff0c;并抑制高频信号。其核心思想是在频率域上通过移除高频成分来平滑信号。这在去噪、平滑和提取基本频率成分时非常有用。 离散低通滤波方法通常采用一阶低通滤波器进行处理。一阶低通滤波器是一种常见的数字滤波器&#xff0c;能够将信号…

电脑出现xinput1_3.dll的错误提示怎么办?有什么办法可以解决

电脑如果缺失了xinput1_3.dll还是一件比较复杂的事情&#xff0c;那么电脑出现xinput1_3.dll的错误提示怎么办&#xff0c;又有什么办法可以解决xinput1_3.dll&#xff1f;今天我们就来聊聊xinput1_3.dll丢失的解决办法&#xff0c;来看看都有哪些办法可以解决吧。 一.常见的问…

Leetcode—2331.计算布尔二叉树的值【简单】

2023每日刷题&#xff08;六&#xff09; Leetcode—2331.计算布尔二叉树的值 递归实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ bool evaluateTree(struct TreeNod…

rabbitMQ(3)

RabbitMq 交换机 文章目录 1. 交换机的介绍2. 交换机的类型3. 临时队列4. 绑定 (bindings)5. 扇形交换机&#xff08;Fanout &#xff09; 演示6. 直接交换机 Direct exchange6.1 多重绑定6.2 direct 代码案例 7. 主题交换机7.1 Topic 匹配案例7.2 Topic 代码案例 8. headers 头…

ESXI6.5安装教程

设置从IPMI Virtual Disk 3000启动&#xff0c;出现如下界面&#xff1a; 默认选择第一项&#xff0c;回车安装 安装程序正在检测服务器硬件信息&#xff0c;如果不满足系统安装条件会跳出错误提示。 检测完成之后会出现下面界面 回车 按F11 这里列出了服务器硬盘信息&#…

安防视频监控系统EasyCVR视频汇聚存储平台定制化开发:新增kafka配置

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台可拓展性强、…

Flyway Desktop updated

Flyway Desktop updated 为比较工件序列化和反序列化添加了额外的调试日志记录。 Flyway Desktop现在将记住以前用于创建项目和匹配克隆的位置。 新的脱机许可工作流现在已在Microsoft Windows上启用。 现在&#xff0c;在配置目标数据库列表时&#xff0c;环境ID是可见的。 现…

c++ pcl点云变换骨架枝干添加树叶源码实例

程序示例精选 c pcl点云变换骨架枝干添加树叶源码实例 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《c pcl点云变换骨架枝干添加树叶源码实例》编写代码&#xff0c;代码整洁&#xff0c;…

文具办公品经营小程序商城的作用是什么

在购买文具方面&#xff0c;线下文具品牌门店除了疫情冲击外&#xff0c;还有同行间的激烈竞争&#xff0c;消费者对品牌概念较为模糊&#xff0c;同质化产品严重&#xff0c;消费者选择度高&#xff0c;店铺流量稀少&#xff0c;线下经营成本变高。 如今很多消费者已经习惯于线…