李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
GO
正文
07.Golang容器之切片功能操作
Leefs
2022-06-30 PM
1250℃
0条
[TOC] ### 前言 ![07.GO语言容器之切片功能操作01.jpeg](https://lilinchao.com/usr/uploads/2022/06/1869946314.jpeg) ### 一、切片不能直接比较 切片之间是不能比较的,我们不能使用`==`操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和`nil`比较。 一个`nil`值的切片并没有底层数组,一个`nil`值的切片的长度和容量都是0。 但是我们不能说一个长度和容量都是0的切片一定是`nil`,例如下面的示例: ```go var s1 []int //len(s1)=0;cap(s1)=0;s1==nil s2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nil s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil ``` 所以要判断一个切片是否是空的,要是用`len(s) == 0`来判断,不应该使用`s == nil`来判断。 ### 二、切片的赋值拷贝 下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。 ```go func main() { s1 := make([]int, 3) //[0 0 0] s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组 s2[0] = 100 fmt.Println(s1) //[100 0 0] fmt.Println(s2) //[100 0 0] } ``` 由于切片是引用类型,所以s1和s2其实都指向了同一块内存地址。修改s2的同时s1的值也会发生变化。 ### 三、使用copy()函数复制切片 Go语言内建的`copy()`函数可以迅速地将一个切片的数据复制到另外一个切片空间中,`copy()`函数的使用格式如下: ```go copy( destSlice, srcSlice []T) int ``` - srcSlice: 数据来源切片 - destSlice: 目标切片 copy()函数就是将 srcSlice 复制到 destSlice,目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy() 函数的返回值表示实际发生复制的元素个数。 **示例** ```go func main(){ slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3} //copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中 copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置 for _, value := range slice1 { fmt.Printf("%d \t", value) } //for _, value := range slice2 { // fmt.Printf("%d \t", value) //} } ``` 虽然通过循环复制切片元素更直接,不过内置的 copy() 函数使用起来更加方便,copy() 函数的第一个参数是要复制的目标 slice,第二个参数是源 slice,两个 slice 可以共享同一个底层数组,甚至有重叠也没有问题。 **示例** ```go func main() { // 设置元素数量为1000 const elementCount = 1000 // 预分配足够多的元素切片 srcData := make([]int, elementCount) // 将切片赋值 for i := 0; i < elementCount; i++ { srcData[i] = i } // 引用切片数据 refData := srcData // 预分配足够多的元素切片 copyData := make([]int, elementCount) // 将数据复制到新的切片空间中 copy(copyData, srcData) // 修改原始数据的第一个元素 srcData[0] = 999 // 打印引用切片的第一个元素 fmt.Println(refData[0]) // 打印复制切片的第一个和最后一个元素 fmt.Println(copyData[0], copyData[elementCount-1]) // 复制原始数据从4到6(不包含) copy(copyData, srcData[4:6]) for i := 0; i < 5; i++ { fmt.Printf("%d ", copyData[i]) } } ``` **运行结果** ``` 999 0 999 4 5 2 3 4 ``` ### 四、append()方法为切片添加元素 Go语言的内建函数`append()`可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。 ```go func main(){ var s []int s = append(s, 1) // [1] s = append(s, 2, 3, 4) // [1 2 3 4] s2 := []int{5, 6, 7} s = append(s, s2...) // [1 2 3 4 5 6 7] } ``` **注意:**通过var声明的零值切片可以在`append()`函数直接使用,无需初始化。 ``` var s []int s = append(s, 1, 2, 3) ``` 没有必要像下面的代码一样初始化一个切片再传入`append()`函数使用。 ```go s := []int{} // 没有必要初始化 s = append(s, 1, 2, 3) var s = make([]int) // 没有必要初始化 s = append(s, 1, 2, 3) ``` 每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在`append()`函数调用时,所以我们通常都需要用原变量接收append函数的返回值。 **示例** ```go func main() { //append()添加元素和切片扩容 var numSlice []int for i := 0; i < 10; i++ { numSlice = append(numSlice, i) fmt.Printf("%v len:%d cap:%d ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice) } } ``` **运行结果** ``` [0] len:1 cap:1 ptr:0xc00009e058 [0 1] len:2 cap:2 ptr:0xc00009e0a0 [0 1 2] len:3 cap:4 ptr:0xc00009c140 [0 1 2 3] len:4 cap:4 ptr:0xc00009c140 [0 1 2 3 4] len:5 cap:8 ptr:0xc0000b2100 [0 1 2 3 4 5] len:6 cap:8 ptr:0xc0000b2100 [0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc0000b2100 [0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc0000b2100 [0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc0000d0080 [0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc0000d0080 ``` 从上面的结果可以看出: 1. `append()`函数将元素追加到切片的最后并返回该切片。 2. 切片numSlice的容量按照1,2,4,8,16这样的规则自动进行扩容,每次扩容后都是扩容前的2倍。 append()函数还支持一次性追加多个元素。 **示例** ```go var citySlice []string // 追加一个元素 citySlice = append(citySlice, "北京") // 追加多个元素 citySlice = append(citySlice, "上海", "广州", "深圳") // 追加切片 a := []string{"成都", "重庆"} citySlice = append(citySlice, a...) fmt.Println(citySlice) //[北京 上海 广州 深圳 成都 重庆] ``` ### 五、从切片中删除元素 Go语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。 #### 从开头位置删除 删除开头的元素可以直接移动数据指针: ```go a = []int{1, 2, 3} a = a[1:] // 删除开头1个元素 a = a[N:] // 删除开头N个元素 ``` 也可以不移动数据指针,但是将后面的数据向开头移动,可以用 append 原地完成(所谓原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化): ```go a = []int{1, 2, 3} a = append(a[:0], a[1:]...) // 删除开头1个元素 a = append(a[:0], a[N:]...) // 删除开头N个元素 ``` 还可以用 copy() 函数来删除开头的元素: ```go a = []int{1, 2, 3} a = a[:copy(a, a[1:])] // 删除开头1个元素 a = a[:copy(a, a[N:])] // 删除开头N个元素 ``` #### 从中间位置删除 对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用 append 或 copy 原地完成: ```go a = []int{1, 2, 3, ...} a = append(a[:i], a[i+1:]...) // 删除中间1个元素 a = append(a[:i], a[i+N:]...) // 删除中间N个元素 a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素 a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素 ``` #### 从尾部删除 ```go a = []int{1, 2, 3} a = a[:len(a)-1] // 删除尾部1个元素 a = a[:len(a)-N] // 删除尾部N个元素 ``` 删除开头的元素和删除尾部的元素都可以认为是删除中间元素操作的特殊情况,下面来看一个示例。 **示例** 删除切片指定位置的元素 ```go func main(){ seq := []string{"a", "b", "c", "d", "e"} // 指定删除位置 index := 2 // 查看删除位置之前的元素和之后的元素 fmt.Println(seq[:index], seq[index+1:]) // 将删除点前后的元素连接起来 seq = append(seq[:index], seq[index+1:]...) fmt.Println(seq) } ``` **运行结果** ``` [a b] [d e] [a b d e] ``` 代码的删除过程可以使用下图来描述。 ![07.GO语言容器之切片功能操作02.jpg](https://lilinchao.com/usr/uploads/2022/06/3517644763.jpg) Go语言中删除切片元素的本质是,以被删除元素为分界点,将前后两个部分的内存重新连接起来。 ### 六、切片的扩容策略 可以通过查看`$GOROOT/src/runtime/slice.go`源码,其中扩容相关代码如下: ```go newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.len < 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 } } } ``` 从上面的代码可以看出以下内容: - 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。 - 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap), - 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap) - 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。 需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如`int`和`string`类型的处理方式就不一样。 *附参考文章链接* *https://www.liwenzhou.com/posts/Go/06_slice/* *http://c.biancheng.net/view/30.html*
标签:
Golang基础
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2204.html
上一篇
06.Golang容器之切片
下一篇
08.Golang容器之Map
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
设计模式
锁
BurpSuite
ajax
JavaWeb
JVM
微服务
FastDFS
CentOS
并发编程
Jquery
Netty
Shiro
GET和POST
栈
Elasticsearch
ClickHouse
哈希表
算法
Scala
pytorch
SpringBoot
字符串
NIO
LeetCode刷题
Flume
Redis
随笔
Java
SpringCloudAlibaba
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭