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 怎么使用?
相关文章
|
1月前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
|
1月前
|
存储 JSON 前端开发
一文搞懂 Go 1.21 的日志标准库 - slog
一文搞懂 Go 1.21 的日志标准库 - slog
34 2
|
17天前
|
设计模式 Java 数据库连接
|
17天前
|
Go 开发者
|
17天前
|
XML Go 数据库
|
17天前
|
设计模式 编译器 程序员
|
20天前
|
存储 机器学习/深度学习 设计模式
Go从入门到放弃之函数
Go从入门到放弃之函数
|
存储 JSON JavaScript
Go 有别于其他语言的九个特性
Go 有别于其他语言的九个特性
319 0
|
7天前
|
程序员 Go PHP
为什么大部分的 PHP 程序员转不了 Go 语言?
【9月更文挑战第8天】大部分 PHP 程序员难以转向 Go 语言,主要因为:一、编程习惯与思维方式差异,如语法风格和编程范式;二、学习成本高,需掌握新知识体系且面临项目压力;三、职业发展考量,现有技能价值及市场需求不确定性。学习新语言虽有挑战,但对拓宽职业道路至关重要。
35 10
|
5天前
|
Go API 开发者
深入探讨:使用Go语言构建高性能RESTful API服务
在本文中,我们将探索Go语言在构建高效、可靠的RESTful API服务中的独特优势。通过实际案例分析,我们将展示Go如何通过其并发模型、简洁的语法和内置的http包,成为现代后端服务开发的有力工具。