深入理解go语言中的切片

写在文章开头

从一个Java的开发角度来看,切片我们可以理解为Java中的ArrayList即一种动态数组的实现,本文会从源码的角度对切片进行深入剖析,希望对你有帮助。

在这里插入图片描述

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

详解go语言切片

切片的底层结构

我们从切片的实现类slice.go中可以看到其结构体,其本质是通过一个指针array 指向数组,然后用len记录当前使用的长度以及数组的对应容量:

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

就像下面这张图一样,这就是一个len为3,而cap即容量为5的切片的样子。

在这里插入图片描述

字面量创建切片

创建切片的方式分别又字面量和常量两种方式创建,字面量创建切片:

	s := []int{1, 2, 3, 4, 5}

然后运行下面这段命令查看生成的汇编码:

go build -gcflags -S main.go

这样一段简单的代码,执行了3个步骤:

  1. 创建长度为5的数组的指针。
  2. 通过newobject构建切片对象。
  3. 分配lencap两个变量的地址空间,这两个变量分别记录当前切片已使用长度和总容量。
		# 创建一个数组指针
		 0x0018 00024 (F:\github\test\main.go:7) LEAQ    type:[5]int(SB), AX
        0x001f 00031 (F:\github\test\main.go:7) PCDATA  $1, $0
        0x001f 00031 (F:\github\test\main.go:7) NOP
		# 构建切片对象
        0x0020 00032 (F:\github\test\main.go:7) CALL    runtime.newobject(SB)
        0x0025 00037 (F:\github\test\main.go:7) MOVQ    $1, (AX)
        # 分配len和cap的空间地址
        0x002c 00044 (F:\github\test\main.go:7) MOVQ    $2, 8(AX)
        0x0034 00052 (F:\github\test\main.go:7) MOVQ    $3, 16(AX)


make语法创建切片

我们也可以用make创建一个切片,如下所示,这就是创建一个长度为1且容量为1的切片:

	// 创建一个长度为5,容量为5的整数切片
	slice := make([]int, 1, 1)
	slice = append(slice, 1)
	fmt.Println(slice)

通过指令查看查看汇编码,可以看到使用make创建切片本质是调用了makeslice方法:

 0x0018 00024 (F:\github\test\main.go:7) LEAQ    type:int(SB), AX
0x001f 00031 (F:\github\test\main.go:7) MOVL    $10, BX
0x0024 00036 (F:\github\test\main.go:7) MOVQ    BX, CX
0x0027 00039 (F:\github\test\main.go:7) PCDATA  $1, $0
0x0027 00039 (F:\github\test\main.go:7) CALL    runtime.makeslice(SB)

这里我们也给出slice.gomakeslice方法的基本框架:

func makeslice(et *_type, len, cap int) unsafe.Pointer {
	mem, overflow := math.MulUintptr(et.size, uintptr(cap))
	//.......

	return mallocgc(mem, et, true)
}

切片的扩容机制

都说切片会动态扩容,这里我们创建一个容量为10的切片,在容量以内添加元素,其容量和size都没有变化,一旦追加元素就会触发扩容,可以看到此时已用size为11,容量扩容为原来的1倍,最终容量变为20:

func main() {
	//len和cap都为10
	slice := make([]int, 10)
	fmt.Println(len(slice))
	fmt.Println(cap(slice))
	//len和cap都为10
	slice[0] = 1
	fmt.Println(len(slice))
	fmt.Println(cap(slice))
	//追加一个 len为11 cap为20(两倍扩容)
	s1 := append(slice, 11)
	fmt.Println(len(s1))
	fmt.Println(cap(s1))

}

我们通过slice.go定位到了扩容的方法growslice即看到扩容的代码,常规情况下如果追加后的长度大于原有容量的2倍,那么lencap都为新长度:

在这里插入图片描述

反之若追加后的元素小于容量的2倍,则直接进行2倍扩容即可:

在这里插入图片描述

若新的长度大于容量的2倍则判断旧的容量是否超256,则会按照累加(256*3+(大于256的新长度))/4得到一个值,这一点我们不妨使用极限思维来分析,假设我们追加后的长度为257,按照这个公式我们得出:

1. 旧的容量为256
2. 追加后的长度为257
3. 由公式得新容量等于(256*3+257)/4即找到区域256的最大值,最终得到256.25+旧容量256取整后为512
4. 基于此计算方式我们不断进行推算,得到10w扩容为125192无限趋近于1.25倍,由此可以得出大于256后的扩容会由2倍扩容逐步转为1.25倍扩容的稳定过度。。

这一点我们也可以从growslice的源码中得以印证:

func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice {

	//......

	//默认新容量为oldCap即旧的容量值
	newcap := oldCap
	//获取当前容量的2倍
	doublecap := newcap + newcap
	//如果添加新元素后的len大于当前容量2倍则取newLen
	if newLen > doublecap {
		newcap = newLen
	} else {
		const threshold = 256
		//旧容量小于256则两倍扩容
		if oldCap < threshold {
			newcap = doublecap
		} else {
			//通过该公式实现随着容量不断递增,扩容逐渐递减,得到一个从2倍到1.25倍扩容的一个过渡
			for 0 < newcap && newcap < newLen {
				newcap += (newcap + 3*threshold) / 4
			}
			
			if newcap <= 0 {
				newcap = newLen
			}
		}
	}

	//.......
}

小结

本文通过切片的创建结合汇编码了解的切片底层数据结构和创建过程,再通过代码示例结合源码的方式了解了切片的动态扩容机制,了解切片在扩容时如何在空间和时间上实现折中,希望对你有帮助。

我是 sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端框架模板

前端框架模板 1、vue-element-admin vue-element-admin是基于element-ui 的一套后台管理系统集成方案。 **功能&#xff1a;**https://panjiachen.github.io/vue-element-admin-site/zh/guide/#功能 **GitHub地址&#xff1a;**GitHub - PanJiaChen/vue-element-admin: :t…

Win 运维 | Windows Server 系统事件日志浅析与日志审计实践

[ 重剑无锋&#xff0c;大巧不工。] 大家好&#xff0c;我是【WeiyiGeek/唯一极客】一个正在向全栈工程师(SecDevOps)前进的技术爱好者 作者微信&#xff1a;WeiyiGeeker 公众号/知识星球&#xff1a;全栈工程师修炼指南 主页博客: 【 https://weiyigeek.top 】- 为者常成&…

【Mamba】医学图像分割的Mamba评估指标参考(更新中...)

医学图像分割的Mamba评估指标参考 VM-UNet&VM-UNetV2LightM-UNetUltraLight-VM-UNetH-vmunetU-MambaLMa-UNetMamba-UNetVM-UNet&VM-UNetV2 DatasetModelmIoU(%)↑DSC(%)↑Acc(%)↑Spe(%)↑Sen(%)↑ISIC17UNet76.9886.9995.6597.4386.82ISIC17UTNetV277.3587.2395.8498.…

Java开发从入门到精通(十):Java常用的API编程接口:String

Java大数据开发和安全开发 (一)Java常用的API1.1 什么是API1.2 什么叫做包1.2.1 如何调用其他包的程序 1.3 JAVA的API&#xff1a;String1.3.1 创建String对象1.3.2 String提供的操作字符串数据的常用方法1.3.3 String练习案例 (一)Java常用的API 1.1 什么是API API(全称 App…

Ubuntu安装VMVare Workstation pro 17.5.1

由于需要装Kali&#xff0c;我电脑是Ubuntu单系统&#xff0c;所以只能使用linux版本的虚拟机&#xff0c;通过这种方式来安装虚拟机和Kali镜像。 参考CSDN博客资料&#xff1a;https://blog.csdn.net/xiaochong0302/article/details/127420124 github代码资料&#xff1a;vm…

2024 CKA 基础操作教程(十二)

题目内容 考点相关内容分析 Pods Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。 Pod 是 Kubernetes 中的原子单元&#xff0c;用于封装应用程序的一个或多个容器、存储资源、唯一的网络 IP&#xff0c;以及有关如何运行容器的选项。Pod 提供了一个共享的…

如何实现超大场景三维模型数据立体裁剪

如何实现超大场景三维模型数据立体裁剪 实现超大场景三维模型数据的立体裁剪可以采用如下方法&#xff1a; 数据预处理&#xff1a;将超大场景三维模型数据进行划分和分割&#xff0c;将其拆分成多个小块或网格。这样可以方便进行后续的裁剪操作。 裁剪算法选择&#xff1a;根据…

场景文本检测识别学习 day04(目标检测的基础概念)

经典的目标检测方法 one-stage 单阶段法&#xff1a;YOLO系列、SSD系列 one-stage方法&#xff1a;仅预测一次&#xff0c;直接在特征图上预测每个物体的类别和边界框输入图像之后&#xff0c;使用CNN网络提取特征图&#xff0c;不加入任何补充&#xff08;锚点、锚框&#x…

OpenHarmony轻量系统开发【2】源码下载和开发环境

2.1源码下载 关于源码下载的&#xff0c;读者可以直接查看官网&#xff1a; https://gitee.com/openharmony/docs/tree/master/zh-cn/release-notes 本文这里做下总结&#xff1a; &#xff08;1&#xff09;注册码云gitee账号。 &#xff08;2&#xff09;注册码云SSH公钥…

如何采集opc服务器数据上传云端

为了进一步提高生产效率&#xff0c;生产制造的不断朝着智能化发展和升级&#xff0c;传统的自动化生产系统已经不能满足需求。传统的SCADA系统一般是用于现场的数据采集与控制&#xff0c;但是本地控制已经无法满足整个工厂系统智能化数字化的需求&#xff0c;智能化数字化是需…

【Altium Designer 20 笔记】PCB线宽与过孔尺寸

电源线&#xff1a;40mil1A&#xff08;一般翻倍给&#xff09;,地线比电源线粗一点即可&#xff1b;信号线&#xff1a;10-15mil 一、线宽 市电的火线和零线&#xff1a;80-100mil12V /24V 20mil~60mil 5V 20-30mil 3V 20-30mil GND 越宽越好20-30mil普通信号线 10mil-15mil…

Qt 3 QVariant类的使用和实例

QVariant, 类本质为 C联合(Union)数据类型&#xff0c;它可以保存很多Qt 类型的值&#xff0c;包括 QBrush、QColor、QString 等等。也能够存放Qt的容器类型的值。QVariant::StringList 是 Qt定义的一个 QVariant::type 枚举类型的变量&#xff0c;其他常用的枚举类型变量如下表…

LabVIEW变速箱自动测试系统

LabVIEW变速箱自动测试系统 在农业生产中&#xff0c;采棉机作为重要的农用机械&#xff0c;其高效稳定的运行对提高采棉效率具有重要意义。然而&#xff0c;传统的采棉机变速箱测试方法存在测试效率低、成本高、对设备可能产生损害等问题。为了解决这些问题&#xff0c;开发了…

网站添加PWA支持,仅需三步,无视框架的类型

总结起来&#xff0c;网站配置PWA简单步骤为&#xff1a; 编写 manifest.json&#xff1b;编写 serviceWorker.js&#xff1b;在 index.html 引入上述两个文件&#xff1b;把上述三个文件放在网站根目录(或者同一目录下)&#xff1b;网站需要部署在https环境才能触发&#xff…

偏微分方程算法之二维初边值问题(交替方向隐(ADI)格式)

一、研究对象 以二维抛物型方程初边值问题为研究对象&#xff1a; 为了确保连续性&#xff0c;公式&#xff08;1&#xff09;中的相关函数满足&#xff1a; 二、理论推导 2.1 向前欧拉格式 首先进行网格剖分。将三维长方体空间&#xff08;二维位置平面一维时间轴&#xff09…

微服务之CircuitBreaker断路器

一、概述 1.1背景 在一个分布式系统中&#xff0c;每个服务都可能会调用其它的服务器&#xff0c;服务之间是相互调用相互依赖。假如微服务A调用微服务B和微服务C&#xff0c;微服务B和微服务C又调用其他的微服务。这就是构成所谓“扇出”。 如果扇出的链路上某个微服务的调…

图数据库Neo4J入门——Neo4J下载安装+Cypher基本操作+《西游记》人物关系图实例

这里写目录标题 一、效果图二、环境准备三、数据库设计3.1 人物节点设计3.2 关系设计 四、操作步骤4.1 下载、安装、启动Neo4J服务4.1.1 配置Neo4J环境变量4.1.2 启动Neo4J服务器4.1.3 启动Ne04J客户端 4.2 创建节点4.3 创建关系&#xff08;从已有节点创建关系&#xff09;4.4…

数据结构和算法(哈希表和图(A*算法精讲))

一 、哈希表 1.1 哈希表原理精讲 哈希表-散列表&#xff0c;它是基于快速存取的角度设计的&#xff0c;也是一种典型的“空间换时间”的做法 键(key)&#xff1a; 组员的编号如&#xff0c;1、5、19。。。 值(value)&#xff1a; 组员的其它信息&#xff08;包含性别、年龄和…

并查集练习

前言&#xff1a; 关于并查集的一些训练题。 正文&#xff1a; 1.亲戚&#xff1a; #include<bits/stdc.h> using namespace std; const int N5005; int fa[N]; int find(int x){if(xfa[x])return x;return fa[x]find(fa[x]); } void merge(int x,int y){fa[find(x)]fi…

MajorDoMo thumb.php 未授权RCE漏洞复现(CNVD-2024-02175)

0x01 产品简介 MajorDoMo是MajorDoMo社区的一个开源DIY智能家居自动化平台。 0x02 漏洞概述 MajorDoMo /modules/thumb/thumb.php接口处存在远程命令执行漏洞&#xff0c;未经身份验证的攻击者可利用此漏洞执行任意指令&#xff0c;获取服务器权限。 0x03 影响范围 MajorD…