Go 1.22 标准库 slices 新增函数和一些旧函数增加新特性

简介: Go 1.22 标准库 slices 新增函数和一些旧函数增加新特性

01


介绍


Go 1.21 标准库中新增的 slices 提供了很多方便处理 slice 的函数。


Go 1.22 标准库 slices 引入一些新特性,其中包括新增函数 Concat、优化函数 DeleteDeleteFuncCompactCompactFuncReplaceInsert


本文我们介绍 Go 1.22 标准库 slices 的新增函数和优化旧函数的新特性,关于它们原有的一些特性,本文不再赘述。


02


新增函数 Concat


新函数 Concat 连接多个切片,返回一个连接多个切片的切片


示例代码:


func main() {
 s1 := []int{1, 2, 3}
 s2 := []int{4, 5, 6}
 s3 := []int{7, 8, 9}
 s := slices.Concat(s1, s2, s3)
 fmt.Println(s)
}


输出结果:


[1 2 3 4 5 6 7 8 9]


阅读上面这段代码,我们定义 3 个切片类型的变量 s1s2s3,使用 go 1.22 标准库 slices 新增函数 Concat 拼接为 1 个切片。


在此之前,我们想要拼接 3 个切片为一个切片,需要使用 append 内置函数。


示例代码:


func main() {
 s1 := []int{1, 2, 3}
 s2 := []int{4, 5, 6}
 s3 := []int{7, 8, 9}
 //s := slices.Concat(s1, s2, s3)
 var s []int
 s = append(s, s1...)
 s = append(s, s2...)
 s = append(s, s3...)
 fmt.Println(s)
}


输出结果:


[1 2 3 4 5 6 7 8 9]


阅读上面这段代码,我们使用内置函数 append 拼接 3 个切片为 1 个切片,需要先定义 1 个切片类型的新变量 s,然后使用内置函数 append 每次追加 1 个切片类型的变量到变量 s,实现方式比较繁琐。


源码实现:


// Concat returns a new slice concatenating the passed in slices.
func Concat[S ~[]E, E any](slices ...S) S {
 size := 0
 for _, s := range slices {
  size += len(s)
  if size < 0 {
   panic("len out of range")
  }
 }
 newslice := Grow[S](nil, size)
 for _, s := range slices {
  newslice = append(newslice, s...)
 }
 return newslice
}


阅读源码,我们可以发现,func Concat[S ~[]E, E any](slices ...S) S 函数的实现比较简单,并且是基于泛型实现,不需要每个类型都实现一个对应的函数。


需要注意的是,它在拼接多个切片之前,先计算新切片的长度,然后使用 Grow 函数创建一个新切片,作为内置函数 append 的参数,这样可以避免内存分配。


为了避免触发 Grow 函数的 panic,它先对 Grow 函数的参数 size 进行了小于 0 的判断,感兴趣的读者朋友可以阅读 Grow 函数的源码


03


元素归零优化


Go 1.22 优化缩小切片大小的函数,将新长度和旧长度之间的元素归零,包括函数 DeleteDeleteFuncCompactCompactFuncReplace


我们通过示例代码,分别在 Go 1.22 和 Go 1.21 中运行,查看运行结果的变化。


Delete


标准库 slices 的函数 func Delete[S ~[]E, E any](s S, i, j int) S 删除切片 ss[i:j] 中的元素,返回修改后的切片。


示例代码:


func main() {
 s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
 s2 := slices.Delete(s1, 2, 5)
 fmt.Printf("len=%d\tcap=%d\tval=%d\n", len(s1), cap(s1), s1)
 fmt.Printf("len=%d\tcap=%d\tval=%d\n", len(s2), cap(s2), s2)
}


输出结果:


// go 1.22
len=9   cap=9   val=[1 2 6 7 8 9 0 0 0]
len=6   cap=9   val=[1 2 6 7 8 9]
// go 1.21
len=9 cap=9 val=[1 2 6 7 8 9 7 8 9]
len=6 cap=9 val=[1 2 6 7 8 9]


阅读上面这段代码,我们可以发现 Go 1.22 标准库 slices 中的函数 Delete 将新切片长度 6 和旧切片长度 9 之间的元素改为切片元素的类型零值。


DeleteFunc


标准库 slices 的函数 func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S 在切片 s 中删除函数类型参数 del 返回值为 true 的任意元素,返回修改后的切片。


示例代码:


func main() {
 s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
 s2 := slices.DeleteFunc(s1, func(i int) bool {
  return i%2 != 0
 })
 fmt.Printf("len=%d\tcap=%d\tval=%d\t\n", len(s1), cap(s1), s1)
 fmt.Printf("len=%d\tcap=%d\tval=%d\t\n", len(s2), cap(s2), s2)


输出结果:


// go 1.22
len=9   cap=9   val=[2 4 6 8 0 0 0 0 0] 
len=4   cap=9   val=[2 4 6 8]
// go 1.21
len=9 cap=9 val=[2 4 6 8 5 6 7 8 9] 
len=4 cap=9 val=[2 4 6 8]


阅读上面这段代码,我们可以发现 Go 1.22 标准库 slices 中的函数 DeleteFunc 将新切片长度 4 和旧切片长度 9 之间的元素改为切片元素的类型零值。


Compact


标准库 slices 的函数 func Compact[S ~[]E, E comparable](s S) S 将切片中连续重复的元素去重。Compact 修改切片的内容并返回修改后的切片,该切片的长度可能比原切片的长度小。


示例代码:


func main() {
 s1 := []int{1, 2, 2, 3, 4, 3, 5, 4}
 s2 := slices.Compact(s1)
 fmt.Printf("len=%d\tcap=%d\tval=%d\t\n", len(s1), cap(s1), s1)
 fmt.Printf("len=%d\tcap=%d\tval=%d\t\n", len(s2), cap(s2), s2)
}


输出结果:


// go 1.22
len=8   cap=8   val=[1 2 3 4 3 5 4 0]   
len=7   cap=8   val=[1 2 3 4 3 5 4]
// go 1.21
len=8 cap=8 val=[1 2 3 4 3 5 4 4] 
len=7 cap=8 val=[1 2 3 4 3 5 4]


阅读上面这段代码,我们可以发现 Go 1.22 标准库 slices 中的函数 Compact 将新切片长度 7 和旧切片长度 8 之间的元素改为切片元素的类型零值。


CompactFunc


标准库 slices 的函数 func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S 在切片 s 中将函数类型参数 eq 返回值为 true 的两个元素去重,返回修改后的切片。


示例代码:


func main() {
 s1 := []string{"go", "php", "php", "java", "python"}
 s2 := slices.CompactFunc(s1, strings.EqualFold)
 fmt.Printf("len=%d\tcap=%d\tval=%#v\n", len(s1), cap(s1), s1)
 fmt.Printf("len=%d\tcap=%d\tval=%#v\n", len(s2), cap(s2), s2)
}


输出结果:


// go 1.22
len=5   cap=5   val=[]string{"go", "php", "java", "python", ""}
len=4   cap=5   val=[]string{"go", "php", "java", "python"}
// go 1.21
len=5 cap=5 val=[]string{"go", "php", "java", "python", "python"}
len=4 cap=5 val=[]string{"go", "php", "java", "python"}


阅读上面这段代码,我们可以发现 Go 1.22 标准库 slices 中的函数 CompactFunc 将新切片长度 4 和旧切片长度 5 之间的元素改为切片元素的类型零值。


Replace


标准库 slices 函数 func Replace[S ~[]E, E any](s S, i, j int, v ...E) S 将切片 ss[i:j] 中的元素修改为 v,返回修改后的切片。


示例代码:


func main() {
 s1 := []string{"php", "java", "go", "python"}
 s2 := slices.Replace(s1, 1, 3, "rust")
 fmt.Printf("len=%d\tcap=%d\tval=%#v\n", len(s1), cap(s1), s1)
 fmt.Printf("len=%d\tcap=%d\tval=%#v\n", len(s2), cap(s2), s2)
}


输出结果:


// go 1.22
len=4   cap=4   val=[]string{"php", "rust", "python", ""}
len=3   cap=4   val=[]string{"php", "rust", "python"}
// go 1.21
len=4 cap=4 val=[]string{"php", "rust", "python", "python"}
len=3 cap=4 val=[]string{"php", "rust", "python"}


阅读上面这段代码,,我们可以发现 Go 1.22 标准库 slices 中的函数 Replacelen(v) < (j-i) 时,将新切片长度 2 和旧切片长度 3 之间的元素改为切片元素的类型零值。


04


函数 Insert 越界优化


Go 1.22 优化 Insert 函数,当参数 i 超出切片范围,则 Insert 函数将运行时触发 panic,此前,如果没有要插入的元素,该情况运行时不会触发 panic


我们通过示例代码,分别在 Go 1.22 和 Go 1.21 中运行,查看运行结果的变化。


Insert


标准库 slices 的函数 func Insert[S ~[]E, E any](s S, i int, v ...E) S 在切片 si 索引位置,插入元素 v,返回修改后的切片(s[i:] 处的元素被向上移动以腾出空间)。


例代码:


func main() {
 s1 := []string{"Go", "PHP", "Java", "Rust"}
 s2 := slices.Insert(s1, 5)
 fmt.Printf("len=%d\tcap=%d\tval=%#v\n", len(s1), cap(s1), s1)
 fmt.Printf("len=%d\tcap=%d\tval=%#v\n", len(s2), cap(s2), s2)
}


输出结果:


// go 1.22
panic: runtime error: slice bounds out of range [5:4]
// ...
// go 1.21
len=4 cap=4 val=[]string{"Go", "PHP", "Java", "Rust"}
len=4 cap=4 val=[]string{"Go", "PHP", "Java", "Rust"}


阅读上面这段代码,我们可以发现 Go 1.22 标准库 slices 中的函数 Inserti 超出切片的范围,即使没有插入元素,运行时也会触发 panic


需要注意的是,如果在 i 超出切片的范围时,插入新元素,Go 1.22 和 Go 1.21 运行时都会引发 panic


05


总结


本文我们介绍 Go 1.22 关于标准库 slices 的新增函数 Concat 和一些旧函数的优化。


其中,新增函数 Concat 使连接多个切片为一个切片的实现代码更加优雅;其余优化函数对比 go 1.21 的变化,也需要我们特别关注。


推荐阅读


  1. Prometheus 的查询语言 PromQL 详解
  2. Go 语言使用标准库 sync 包的 mutex 互斥锁解决数据竞态
  3. Golang 语言标准库 sync 包的 RWMutex 读写互斥锁怎么使用?
  4. Golang语言标准库 sync 包的 WaitGroup 怎么使用?
  5. Golang语言标准库 sync 包的 Cond 怎么使用?
相关文章
|
2月前
|
Shell Go API
Go语言grequests库并发请求的实战案例
Go语言grequests库并发请求的实战案例
|
2月前
|
Go
go函数
go函数
33 10
|
15天前
|
安全 Java Go
Go语言的其他特性
【10月更文挑战第10天】Go语言的其他特性
|
24天前
|
Linux 编译器 Go
cgo--在Go中链接外部C库
cgo--在Go中链接外部C库
|
2月前
|
编译器 Go C++
Go to Learn Go之函数
Go to Learn Go之函数
22 0
|
2月前
|
编译器 Go 索引
Go数组、多维数组和切片(动态数组),及常用函数len(),cap(),copy(),append()在切片中的使用
本文介绍了Go语言中数组、多维数组和切片(动态数组)的基本概念和操作,包括数组的定义、初始化、访问,多维数组的定义和访问,以及切片的创建、使用和扩容。同时,还讲解了切片中常用的函数len()、cap()、copy()和append()的使用方法。
|
2月前
|
安全 Go C语言
Go常量的定义和使用const,const特性“隐式重复前一个表达式”,以及iota枚举常量的使用
这篇文章介绍了Go语言中使用`const`定义常量的方法,包括常量的特性“隐式重复前一个表达式”,以及如何使用`iota`实现枚举常量的功能。
|
3月前
|
设计模式 Java 数据库连接
|
3月前
|
Go 开发者
|
7天前
|
存储 前端开发 Go
Go语言中的数组
在 Go 语言中,数组是一种固定长度的、相同类型元素的序列。数组声明时长度已确定,不可改变,支持多种初始化方式,如使用 `var` 关键字、短变量声明、省略号 `...` 推断长度等。数组内存布局连续,可通过索引高效访问。遍历数组常用 `for` 循环和 `range` 关键字。