《Go语言四十二章经》第十二章 切片(slice)

栏目: Go · 发布时间: 6年前

内容简介:《Go语言四十二章经》第十二章 切片(slice)作者:李骁**切片(slice)**是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型(和数组不一样)。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口(这里有关动态窗口的含义,可参考数据库窗口函数的解释)。

《Go语言四十二章经》第十二章 切片(slice)

作者:李骁

12.1 切片(slice)

**切片(slice)**是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型(和数组不一样)。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口(这里有关动态窗口的含义,可参考数据库窗口函数的解释)。

切片是可索引的,并且可以由 len() 函数获取长度。

给定项的切片索引可能比相关数组的相同元素的索引小。和数组不同的是,切片的长度可以在运行时修改,最小为 0 最大为相关数组的长度:切片是一个 长度可变的数组。

切片提供了计算容量的函数 cap() 可以测量切片最长可以达到多少:它等于切片的长度 + 数组除切片之外的长度。如果 s 是一个切片,cap(s) 就是从 s[0] 到数组末尾的数组长度。切片的长度永远不会超过它的容量,所以对于 切片 s 来说该不等式永远成立:0 <= len(s) <= cap(s)。

多个切片如果表示同一个数组的片段,它们可以共享数据;因此一个切片和相关数组的其他切片是共享存储的,相反,不同的数组总是代表不同的存储。数组实际上是切片的构建块。

优点

因为切片是引用,所以它们不需要使用额外的内存并且比使用数组更有效率,所以在 Go 代码中切片比数组更常用。

注意

绝对不要用指针指向 slice,切片本身已经是一个引用类型,所以它本身就是一个指针!

声明切片的格式是: var identifier []type(不需要说明长度)。

一个切片在未初始化之前默认为 nil,长度为 0。

切片的初始化格式是:

var slice1 []type = arr1[start:end]

这表示 slice1 是由数组 arr1 从 start 索引到 end-1 索引之间的元素构成的子集(切分数组,start:end 被称为 slice 表达式)。

切片也可以用类似数组的方式初始化:

var x = []int{2, 3, 5, 7, 11}

这样就创建了一个长度为 5 的数组并且创建了一个相关切片。

当相关数组还没有定义时,我们可以使用 make() 函数来创建一个切片 同时创建好相关数组:

var slice1 []type = make([]type, len)

也可以简写为 slice1 := make([]type, len),这里 len 是数组的长度并且也是 slice 的初始长度。

make 的使用方式是:func make([]T, len, cap),其中 cap 是可选参数。

v := make([]int, 10, 50)

这样分配一个有 50 个 int 值的数组,并且创建了一个长度为 10,容量为 50 的 切片 v,该 切片 指向数组的前 10 个元素。

以上我们列举了三种切片初始化方式,这三种方式都比较常用。

如果从数组或者切片中生成一个新的切片,我们可以使用下面的表达式:

a[low : high : max]

max-high的结果表示容量。

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

这里t的容量(capacity)是5-1=4 ,长度是2。

12.2 切片重组(reslice)

slice1 := make([]type, start_length, capacity)

其中 start_length 作为切片初始长度而 capacity 作为相关数组的长度。

这么做的好处是我们的切片在达到容量上限后可以扩容。改变切片长度的过程称之为切片重组 reslicing,做法如下:slice1 = slice1[0:end],其中 end 是新的末尾索引(即长度)。

当你重新划分一个slice时,新的slice将引用原有slice的数组。如果你忘了这个行为的话,在你的应用分配大量临时的slice用于创建新的slice来引用原有数据的一小部分时,会导致难以预期的内存使用。

package main

import "fmt"

func get() []byte {  
    raw := make([]byte, 10000)
    fmt.Println(len(raw), cap(raw), &raw[0]) // prints: 10000 10000 <byte_addr_x>
    return raw[:3]  // 10000个字节实际只需要引用3个,其他空间浪费
}

func main() {  
    data := get()
    fmt.Println(len(data), cap(data), &data[0]) // prints: 3 10000 <byte_addr_x>
}

为了避免这个陷阱,你需要从临时的slice中拷贝数据(而不是重新划分slice)。

package main

import "fmt"

func get() []byte {  
    raw := make([]byte, 10000)
    fmt.Println(len(raw), cap(raw), &raw[0]) // prints: 10000 10000 <byte_addr_x>
    res := make([]byte, 3)
    copy(res, raw[:3]) // 利用copy 函数复制,raw 可被GC释放
    return res
}

func main() {  
    data := get()
    fmt.Println(len(data), cap(data), &data[0]) // prints: 3 3 <byte_addr_y>
}

func append(s[]T, x ...T) []T 其中 append 方法将 0 个或多个具有相同类型 s 的元素追加到切片后面并且返回新的切片;追加的元素必须和原切片的元素同类型。如果 s 的容量不足以存储新增元素,append 会分配新的切片来保证已有切片元素和新增元素的存储。因此,返回的切片可能已经指向一个不同的相关数组了。append 方法总是返回成功,除非系统内存耗尽了。

append操作如果导致分配新的切片来保证已有切片元素和新增元素的存储,那么新的slice已经和原来slice没有任何关系,即使修改了数据也不会同步。append操作后,有没有生成新的slice需要看原有slice的容量是否足够,请见下面代码。

12.3 陈旧的(Stale)Slices

多个slice可以引用同一个底层数组。比如,当你从一个已有的slice创建一个新的slice时,这就会发生。如果你的应用功能需要这种行为,那么你将需要关注下“走味的”slice。

在某些情况下,在一个slice中添加新的数据,在原有数组无法保持更多新的数据时,将导致分配一个新的数组。而现在其他的slice还指向老的数组(和老的数据)。

package main

import "fmt"

func main() {
	s1 := []int{1, 2, 3}
	fmt.Println(len(s1), cap(s1), s1) // 输出 3 3 [1 2 3]
	s2 := s1[1:]
	fmt.Println(len(s2), cap(s2), s2) // 输出 2 2 [2 3]
	for i := range s2 {
		s2[i] += 20
	}
	// s2的修改会影响到数组数据,s1输出新数据
	fmt.Println(s1) // 输出 [1 22 23]
	fmt.Println(s2) // 输出 [22 23]

	s2 = append(s2, 4) // append  导致了slice 扩容

	for i := range s2 {
		s2[i] += 10
	}
	// s1 的数据现在是陈旧的老数据,而s2是新数据,他们的底层数组已经不是同一个了。
	fmt.Println(s1) // 输出[1 22 23]
	fmt.Println(s2) // 输出[32 33 14]
}
程序输出:
3 3 [1 2 3]
2 2 [2 3]
[1 22 23]
[22 23]
[1 22 23]
[32 33 14]

本书《Go语言四十二章经》内容在github上同步地址:https://github.com/ffhelicopter/Go42 本书《Go语言四十二章经》内容在简书同步地址: https://www.jianshu.com/nb/29056963

虽然本书中例子都经过实际运行,但难免出现错误和不足之处,烦请您指出;如有建议也欢迎交流。 联系邮箱:roteman@163.com


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

企业应用架构模式

企业应用架构模式

Martin Fowler / 人民邮电出版社 / 2009 / 79.00元

随着信息技术的广泛应用,系统需要处理的数据量越来越大,企业级软件开发已经渐成主流,而开发人员面临的困难与挑战也是显而易见的。更糟糕的是,这一领域的资料一直非常缺乏。 本书是软件开发大师Martin Fowler的代表作,采用模式的形式系统总结了业界多年积累的经验,被称为“企业级应用开发领域的圣经”,出版以来一直畅销不衰,至今仍然无可替代。作 者在精彩地阐述了企业应用开发和设计中的核心原则基础......一起来看看 《企业应用架构模式》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

html转js在线工具
html转js在线工具

html转js在线工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具