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

这篇具有很好参考价值的文章主要介绍了小白学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基础06-了解切片实现原理并高效使用,go,goland,golang,java,算法
因此,我们可以称切片是数组的“描述符”。切片之所以能在函数参数传递时避免较大性能损耗,是因为它是“描述符”的特性,切片这个描述符是固定大小的,无论底层的数组元素类型有多大,切片打开的窗口有多长。

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

//$GOROOT/src/runtime/slice.go
type slice struct {
 array unsafe.Pointer
 len int
 cap int
}

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

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

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

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

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

s := make([]byte, 5)

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

小白学go基础06-了解切片实现原理并高效使用,go,goland,golang,java,算法
我们看到通过上述语句创建的切片,编译器会自动为切片建立一个底层数组,如果没有在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的内部。

小白学go基础06-了解切片实现原理并高效使用,go,goland,golang,java,算法
还可以通过语法s[low: high]基于已有切片创建新的切片,这被称为切片的reslicing。

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

小白学go基础06-了解切片实现原理并高效使用,go,goland,golang,java,算法

当切片作为函数参数传递给函数时,实际传递的是切片的内部表示,也就是上面的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操作究竟是如何让切片进行动态扩容的。

小白学go基础06-了解切片实现原理并高效使用,go,goland,golang,java,算法
我们看到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的平均操作性 能,减少或消除因动态扩容带来的性能损耗。文章来源地址https://www.toymoban.com/news/detail-696531.html

到了这里,关于小白学go基础06-了解切片实现原理并高效使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Go语言基础之切片

    切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。 切片是一个引用类型,它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合 声明切片类型的基本语法如下: 其中, name:表示变

    2024年02月11日
    浏览(54)
  • 【Go 基础篇】深入探索:Go语言中的切片遍历与注意事项

    嗨,Go语言学习者!在我们的编程旅程中,切片(Slice)是一个极其重要的工具。它可以帮助我们处理各种类型的数据,从而让我们的代码更加灵活和高效。本文将围绕Go语言中切片的遍历方法以及在遍历时需要注意的事项进行探讨,帮助你更好地理解和应用切片。 切片的遍历

    2024年02月11日
    浏览(42)
  • 【STL切片算法文献笔记】一种使用 STL 文件进行高效轮廓构造的改进切片算法

    STL 模型是通过将实体模型曲面细分为三角形而获得的。 镶嵌的精度由弦误差控制,弦误差是镶嵌模型与原始实体模型之间的最大形状差异。 细分实体模型所需的三角形数量是可接受的弦误差和零件几何复杂性的函数。 在 STL 文件创建过程中生成的大量三角形可能会影响对模

    2023年04月09日
    浏览(36)
  • Go 语言为什么建议多使用切片,少使用数组?

    大家好,我是 frank,「Golang 语言开发栈」公众号作者。 01 介绍 在 Go 语言中,数组固定长度,切片可变长度;数组和切片都是值传递,因为切片传递的是指针,所以切片也被称为“引用传递”。 读者朋友们在使用 Go 语言开发项目时,或者在阅读 Go 开源项目源码时,发现很少

    2024年02月03日
    浏览(74)
  • 小白学Go基础01-Go 语言的介绍

    Go 语言对传统的面向对象开发进行了重新思考,并且提供了更高效的复用代码的手段。Go 语言还让用户能更高效地利用昂贵服务器上的所有核心,而且它编译大型项目的速度也很快。 Go 语言开发团队花了很长时间来解决当今软件开发人员面对的问题。开发人员在为项目选择语

    2024年02月10日
    浏览(39)
  • go 语言中 json.Unmarshal([]byte(jsonbuff), &j) 字节切片得使用场景

    struct_tag的使用 在上面的例子看到,我们根据结构体生成的json的key都是大写的,因为结构体名字在go语言中不大写的话,又没有访问权限,这种问题会影响到我们对json的key的名字,所以go官方给出了struct_tag的方法去修改生成json时,对应key的名字 map 生成json json解析到map json解

    2024年01月23日
    浏览(41)
  • go基础10 -字符串的高效构造与转换

    前面提到过,Go原生支持通过+/+=操作符来连接多个字符串以构造一个更长的字符串,并且通过+/+=操作符的字符串连接构造是最自然、开发体验最好的一种。 但Go还提供了其他一些构造字符串的方法,比如: ● 使用fmt.Sprintf; ● 使用strings.Join; ● 使用strings.Builder; ● 使用

    2024年02月09日
    浏览(36)
  • 软件测试常用的7种方法,最后一个是升职加薪关键!(零基础小白转行IT互联网高效进阶)

    一般而言,软件测试方法分为黑盒测试、白盒测试、灰盒测试、静态测试、动态测试、手动测试、自动化测试和探索性测试等类型。 黑盒测试又称功能测试、数据驱动测试或基于需求规格说明书的功能测试。该测试方法验证被测对象使用质量及外部质量表现。 采用黑盒测试

    2024年02月06日
    浏览(47)
  • 《 Arm Compiler 5.06 》__ARM编译器官网下载、安装和使用说明(小白也能懂)

    目录 一、前言 二、官方网站下载 三、我的资源 四、编译器安装在 Keil 软件上  五、Keil选择编译器V5 “ V5.06 update 7(build 960) ” 六、测试 (* ̄︶ ̄)创作不易!期待你们的 点赞、收藏和评论喔。         【Keil MDK-Arm5.37】不再默认安装 《 Arm Compiler 5.06  》 ,而安装的只有最新

    2024年02月03日
    浏览(124)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包