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 怎么使用?
相关文章
|
7月前
|
JavaScript 前端开发 Java
通义灵码 Rules 库合集来了,覆盖Java、TypeScript、Python、Go、JavaScript 等
通义灵码新上的外挂 Project Rules 获得了开发者的一致好评:最小成本适配我的开发风格、相当把团队经验沉淀下来,是个很好功能……
1397 103
|
1月前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
161 1
|
2月前
|
存储 Java Go
对比Java学习Go——函数、集合和OOP
Go语言的函数支持声明与调用,具备多返回值、命名返回值等特性,结合`func`关键字与类型后置语法,使函数定义简洁直观。函数可作为一等公民传递、赋值或作为参数,支持匿名函数与闭包。Go通过组合与接口实现面向对象编程,结构体定义数据,方法定义行为,接口实现多态,体现了Go语言的简洁与高效设计。
|
5月前
|
人工智能 Dart Go
Go语言中的make和new函数的区别及使用场景
本文详细解析了Go语言中`make`和`new`函数的使用方法及区别。`make`用于创建切片、映射和通道等引用类型,返回初始化后的值;`new`用于创建任意类型的零值对象,返回指向该对象的指针。文章通过多个示例说明两者的应用场景,并总结了面试中可能遇到的相关问题,如底层实现、使用场景及优缺点等,帮助读者更好地理解和区分这两个函数。
183 1
|
6月前
|
Go 调度
GO语言函数的内部运行机制分析
以上就是Go语言中函数的内部运行机制的概述,展示了函数在Go语言编程中如何发挥作用,以及Go如何使用简洁高效的设计,使得代码更简单,更有逻辑性,更易于理解和维护。尽管这些内容深入了一些底层的概念,但我希望通过这种方式,将这些理论知识更生动、更形象地带给你,让你在理解的同时找到编程的乐趣。
130 5
|
6月前
|
Go Python
函数的定义与调用 -《Go语言实战指南》
本文介绍了 Go 语言中函数的核心特性与用法,包括基本定义格式、调用方式、多返回值、返回值命名、参数类型简写、可变参数、高阶函数及匿名函数等内容。通过示例代码详细展示了如何定义和使用不同类型的函数,使读者能够全面了解 Go 函数的灵活性与强大功能。
124 12
|
7月前
|
算法 Go
【LeetCode 热题100】深入理解二叉树结构变化与路径特性(力扣104 / 226 / 114 / 543)(Go语言版)
本博客深入探讨二叉树的深度计算、结构变换与路径分析,涵盖四道经典题目:104(最大深度)、226(翻转二叉树)、114(展开为链表)和543(二叉树直径)。通过递归与遍历策略(前序、后序等),解析每题的核心思路与实现方法。结合代码示例(Go语言),帮助读者掌握二叉树相关算法的精髓。下一讲将聚焦二叉树构造问题,欢迎持续关注!
191 10
|
8月前
|
Go 开发者
go-carbon v2.6.0 重大版本更新,轻量级、语义化、对开发者友好的 golang 时间处理库
carbon 是一个轻量级、语义化、对开发者友好的 Golang 时间处理库,提供了对时间穿越、时间差值、时间极值、时间判断、星座、星座、农历、儒略日 / 简化儒略日、波斯历 / 伊朗历的支持
194 3
|
存储 JSON JavaScript
Go 有别于其他语言的九个特性
Go 有别于其他语言的九个特性
392 0
|
3月前
|
Cloud Native 安全 Java
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
287 1