小白学go基础06-了解切片实现原理并高效使用

slice,中文多译为切片,是Go语言在数组之上提供的一个重要的抽象数据类型。在Go语言中,对于绝大多数需要使用数组的场合,切片实现了完美替代。并且和数组相比,切片提供了更灵活、更高效的数据序列访问接口。

切片究竟是什么?

在对切片一探究竟之前,我们先来简单了解一下Go语言中的数组。

Go语言数组是一个固定长度的、容纳同构类型元素的连续序列,因此Go数组类型具有两个属性:元素类型和数组长度

这两个属性都相同的数组类型是等价的。比如以下变量
a、b、c对应的数组类型是三个不同的数组类型:

var a [8]int
var b [8]byte
var c [9]int

数组 a、b对应的数组类型长度属性相同,但元素类型不同(一个是int,另一个是byte);数组 a、c对应的数组类型的元素类型相同,都是int,但数组类型的长度不同(一个是8,另一个是9)。

Go数组是值语义的,这意味着一个数组变量表示的是整个数组,这点与C语言完全不同。在C语言中,数组变量可视为指向数组第一个元素的指针。而在Go语言中传递数组是纯粹的值拷贝,对于元素类型长度较大或元素个数较多的数组,如果直接以数组类型参数传递到函数中会有不小的性能损耗。这时很多人会使用数组指针类型来定义函数参数,然后
将数组地址传进函数,这样做的确可以避免性能损耗,但这是C语言的惯用法,在Go语言中,更地道的方式是使用切片。

切片之于数组就像是文件描述符之于文件。在Go语言中,数组更多是“退居幕后”,承担的是底层存储空间的角色;而切片则走向“前台”,为底层的存储(数组)打开了一个访问的“窗口”。

在这里插入图片描述
因此,我们可以称切片是数组的“描述符”。切片之所以能在函数参数传递时避免较大性能损耗,是因为它是“描述符”的特性,切片这个描述符是固定大小的,无论底层的数组元素类型有多大,切片打开的窗口有多长。

下面是切片在Go运行时(runtime)层面的内部表示:

//$GOROOT/src/runtime/slice.go
type slice struct {array unsafe.Pointerlen intcap int
}

我们看到每个切片包含以下三个字段。

● array:指向下层数组某元素的指针,该元素也是切片的起始元素。

● len:切片的长度,即切片中当前元素的个数。

● cap:切片的最大容量,cap >= len

在运行时中,每个切片变量都是一个runtime.slice结构体类型的实例,我们可以用下面的语句创建一个切片实例s

s := make([]byte, 5)

下图展示了切片s在运行时层面的内部表示:

在这里插入图片描述
我们看到通过上述语句创建的切片,编译器会自动为切片建立一个底层数组,如果没有在make中指定cap参数,那么cap = len,即编译器建立的数组长度为len

我们可以通过语法u[low: high]创建对已存在数组进行操作的切片,这被称为数组的切片化(slicing):

u := [10]byte{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
s := u[3:7]

下图展示了切片s的内部。

在这里插入图片描述
还可以通过语法s[low: high]基于已有切片创建新的切片,这被称为切片的reslicing。

如下图所示:新创建的切片与原切片同样是共享底层数组的,并且通过新切片对数组的修改也会反映到原切片中。

在这里插入图片描述

当切片作为函数参数传递给函数时,实际传递的是切片的内部表示,也就是上面的runtime.slice结构体实例,因此无论切片描述的底层数组有多大,切片作为参数传递带来的性能损耗都是很小且恒定的,甚至小到可以忽略不计,这就是函数在参数中多使用切片而不用数组指针的原因之一。

切片的高级特性:动态扩容

如果仅仅是提供通过下标值来操作元素的类数组操作接口,那么切片也不会在Go中占据重要的位置。Go切片还支持一个重要的高级特性:动态扩容。

零值切片也可以通过append预定义函数进行元素赋
值操作:

var s []byte // s被赋予零值nil
s = append(s, 1)

由于初值为零值,s这个描述符并没有绑定对应的底层数组。而经过append操作后,s显然已经绑定了属于它的底层数组。为了方便查看切片是如何动态扩容的,我们打印出每次append操作后切片s的len和cap值:

// chapter3/sources/slice_append.go
var s []int // s被赋予零值nil
s = append(s, 11)
fmt.Println(len(s), cap(s)) //1 1
s = append(s, 12)
fmt.Println(len(s), cap(s)) //2 2
s = append(s, 13)
fmt.Println(len(s), cap(s)) //3 4
s = append(s, 14)
fmt.Println(len(s), cap(s)) //4 4
s = append(s, 15)
fmt.Println(len(s), cap(s)) //5 8

我们看到切片s的len值是线性增长的,但cap值却呈现出不规则的变化。通过下图我们更容易看清楚多次append操作究竟是如何让切片进行动态扩容的。

在这里插入图片描述
我们看到append会根据切片对底层数组容量的需求对底层数组进行动态调整。

尽量使用cap参数创建切片

append操作是一件利器,它让切片类型部分满足了“零值可用”的理念。但从append的原理中我们也能看到重新分配底层数组并复制元素的操作代价还是挺大的,尤其是当元素较多的情况下。那么如何减少或避免为过多内存分配和复制付出的代价呢?

一种有效的方法是根据切片的使用场景对切片的容量规模进行预估,并在创建新切片时将预估出的切片容量数据以cap参数的形式传递给内置函数make:

s := make([]T, len, cap)

下面是一个使用cap参数和不使用cap参数的切片的性能基准测试:

const sliceSize = 10000
func BenchmarkSliceInitWithoutCap(b *testing.B) {for n := 0; n < b.N; n++ {sl := make([]int, 0)for i := 0; i < sliceSize; i++ {sl = append(sl, i)}}
}
func BenchmarkSliceInitWithCap(b *testing.B) {for n := 0; n < b.N; n++ {sl := make([]int, 0, sliceSize)for i := 0; i < sliceSize; i++ {sl = append(sl, i)}}
}

下面是性能基本测试运行的结果(Go 1.12.7;MacBook Pro:8核i5,16GB内存):

$go test -benchmem -bench=. slice_benchmark_test.go
goos: darwin
goarch: amd64
BenchmarkSliceInitWithoutCap-8 50000 36484 ns/op 386297 B/op 20 allocs/op
BenchmarkSliceInitWithCap-8 200000 9250 ns/op 81920 B/op 1 allocs/op
PASS
ok command-line-arguments 4.163s

由结果可知,使用带cap参数创建的切片进行append操作的平均性能(9250ns)是不带cap参数的切片(36 484ns)的4倍左右,并且每操作平均仅需一次内存分配。

切片是Go语言提供的重要数据类型,也是Gopher日常编码中最常使用的类型之一。切 片是数组的描述符,在大多数场合替代了数组,并减少了数组指针作为函数参数的使用。

append在切片上的运用让切片类型部分支持了“零值可用”的理念,并且append对切 片的动态扩容将Gopher从手工管理底层存储的工作中解放了出来。在可以预估出元素容量的前提下,使用cap参数创建切片可以提升append的平均操作性 能,减少或消除因动态扩容带来的性能损耗。

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

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

相关文章

项目管理工具:实现项目科学管理的利器

什么是项目管理工具 项目管理工具是指用于协助规划、组织、执行和监控项目活动的软件或应用程序。它们提供了一系列功能和工具&#xff0c;帮助项目管理人员和团队有效地管理项目进度、资源分配、任务协作以及沟通等方面的工作。项目管理工具的目标是提高团队的工作效率、优化…

浅谈Mysql读写分离的坑以及应对的方案 | 京东云技术团队

一、主从架构 为什么我们要进行读写分离&#xff1f;个人觉得还是业务发展到一定的规模&#xff0c;驱动技术架构的改革&#xff0c;读写分离可以减轻单台服务器的压力&#xff0c;将读请求和写请求分流到不同的服务器&#xff0c;分摊单台服务的负载&#xff0c;提高可用性&a…

vue 浏览器记住密码后,自动填充账号密码错位

亲测有效&#xff01;&#xff01;! 遇到的场景&#xff1a; 浏览器记住密码后&#xff0c;登录时自动填充账号密码&#xff0c;由于登录时只需要这两个字段所以没问题&#xff0c;见图一&#xff0c;但注册时&#xff0c;账号密码不在一处&#xff0c;见图二 原本账号应该在…

windows编程之线程同步万字总结(创建线程,互斥对象,互斥事件,信号量,关键段,多线程群聊服务器)

文章目录 创建线程方法一_beginthreadex函数讲解使用示例&#xff1a; 方法二CreateThread函数讲解:使用示例: 互斥对象:创建互斥对象CreateMutex 互斥事件介绍创建或打开一个未命名的互斥事件对象 信号量介绍信号量的相关函数使用示例 关键段相关函数错误使用示例正确使用示例…

如何为 Flutter 应用程序创建环境变量

我们为什么需要环境变量&#xff1f; 主要用于存储高级机密数据&#xff0c;如果泄露可能会危及您产品的安全性。这些变量本地存储在每个用户的本地系统中&#xff0c;不应该签入存储库。每个用户都有这些变量的副本。 配置 在根项目中创建一个名为 .env 的文件夹&#xff08…

『PyQt5-Qt Designer篇』| 08 Qt Designer中容器布局和绝对布局的使用

08 Qt Designer中容器布局和绝对布局的使用 1 容器布局1.1 设计容器布局1.2 保存文件并执行2 绝对布局2.1 设计绝对布局2.2 保存文件并执行1 容器布局 1.1 设计容器布局 先拖入一个容器Frame容器,然后拖入几个控件: 把拖入的控件拖入容器中: 选中容器,右键-布局-栅格布局:…

基于OpenCV+LPR模型端对端智能车牌识别——深度学习和目标检测算法应用(含Python+Andriod全部工程源码)+CCPD数据集

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境OpenCV环境Android环境1. 开发软件和开发包2. JDK设置3. NDK设置 模块实现1. 数据预处理2. 模型训练1&#xff09;训练级联分类器2&#xff09;训练无分割车牌字符识别模型 3. APP构建1&#xff09;导入OpenCV库…

把一般数据转换成因子数据格式,做单因子、债券对历史数据回测+获取curl命令+垃圾数据转换成标准行情数据(bardata)

下载curl软件&#xff0c;地址&#xff1a; curl for Windows for 64-bit下载好后解压到文件夹&#xff0c;将里面的bin文件添加到环境变量中&#xff0c;bon文件地址为&#xff1a;C:\Users\59980\curl-8.2.1_7-win64-mingw\bin 打开cmd&#xff0c;输入curl --help,出现下…

博流RISC-V芯片JTAG debug配置与运行

文章目录 1、Windows下安装与配置2、Linux下安装与配置3、芯片默认 JTAG PIN 列表4、命令行运行JTAG5、Eclipse下使用JTAG 1、Windows下安装与配置 CKLink 驱动安装 Windows版驱动下载地址&#xff1a; https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1666331…

华为OD机试 - MELON的难题 - 动态规划(Java 2023 B卷 100分)

目录 一、题目描述二、输入描述三、输出描述四、动态规划五、解题思路六、Java算法源码七、效果展示1、输入2、输出3、说明华为OD机试 2023B卷题库疯狂收录中,刷题点这里 一、题目描述 MELON有一堆精美的雨花石(数量为n,重量各异),准备送给S和W。MELON希望送给俩人的雨花石…

9.2 消息对话框 画板 定时器

#include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {//设置定时器timernew QTimer(this);timeidthis->startTimer(1000);connect(timer,&QTimer::timeout,this,&Widget::timeout_slot);speechernew QTextToSpeech(this);//边框this-&…

【前端】CSS-Flex弹性盒模型布局

目录 一、前言二、Flex布局是什么1、任何一个容器都可以指定为Flex布局2、行内元素也可以使用Flex布局3、Webkit内核的浏览器&#xff0c;必须加上-webkit前缀 三、基本概念四、flex常用的两种属性1、容器属性2、项目属性 五、容器属性1、flex-direction①、定义②、语句1&…