01
介绍
Go 1.21 标准库中新增的 slices
提供了很多方便处理 slice
的函数。
Go 1.22 标准库 slices
引入一些新特性,其中包括新增函数 Concat
、优化函数 Delete
,DeleteFunc
,Compact
,CompactFunc
,Replace
和 Insert
。
本文我们介绍 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 个切片类型的变量 s1
、s2
、s3
,使用 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 优化缩小切片大小的函数,将新长度和旧长度之间的元素归零,包括函数 Delete
,DeleteFunc
,Compact
,CompactFunc
,Replace
。
我们通过示例代码,分别在 Go 1.22 和 Go 1.21 中运行,查看运行结果的变化。
Delete
标准库 slices
的函数 func Delete[S ~[]E, E any](s S, i, j int) S
删除切片 s
中 s[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
将切片 s
中 s[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
中的函数 Replace
当 len(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
在切片 s
的 i
索引位置,插入元素 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
中的函数 Insert
在 i
超出切片的范围,即使没有插入元素,运行时也会触发 panic
。
需要注意的是,如果在 i
超出切片的范围时,插入新元素,Go 1.22 和 Go 1.21 运行时都会引发 panic
。
05
总结
本文我们介绍 Go 1.22 关于标准库 slices
的新增函数 Concat
和一些旧函数的优化。
其中,新增函数 Concat
使连接多个切片为一个切片的实现代码更加优雅;其余优化函数对比 go 1.21 的变化,也需要我们特别关注。