Go 1.22 slices 库的更新:高效拼接、零化处理和越界插入优化

前言

Go 1.22 版本于 202426 日发布,引入了几个重要的特性和改进。在标准库层面上,该版本对 slices 库进行了更新,更新内容包括以下三个方面:

  • 新增 Concat 函数:该函数能够高效地拼接多个切片。
  • 零化处理:DeleteDeleteFuncCompactCompactFuncReplace 函数在原切片中将 被移除的元素 置为零值(被移除的元素 是指从原切片中移除的指定元素,在新切片中不存在)。
  • 越界插入优化:在使用 Insert 函数时,若参数 i 超出切片的范围,则总会触发 panic。而在 Go 1.22 版本之前,即使 i 越界了,在没有指定插入元素的情况下,该行为不会触发 panic

本文将详细介绍 Go 语言 slices 库在 Go 1.22 版本中的更新内容。

准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。

新增函数 Concat:高效拼接切片

Concat 函数接受一个不定参数 slices,参数类型为切片,该函数用于将多个切片拼接到一个新的切片里并返回新切片。

代码示例:

go 复制代码
package main

import (
	"fmt"
	"slices"
)

func main() {
	s1 := []string{"Go slices", "Go maps"}
	s2 := []string{"Go strings", "Go strconv"}
	s3 := []string{"程序员", "陈明勇"}
	s4 := slices.Concat(s1, s2, s3)
	fmt.Printf("cap: %d, len: %d\n", cap(s4), len(s4))
	fmt.Println(s4)
}

代码运行结果如下所示:

bash 复制代码
cap: 6, len: 6
[Go slices Go maps Go strings Go strconv 程序员 陈明勇]

根据运行结果可知,Concat 函数将所给定的切片集的元素都拼接到一个新切片里,并且新切片的容量和长度是所给定切片集长度的总和。

我们来看看 Concat 函数的源码实现:

go 复制代码
// 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
}

Concat 函数的源码实现非常简洁,它在拼接切片之前先计算了新切片所需的长度,然后利用 Grow 函数初始化新切片。这样做的好处是避免了后续 append 操作中因为切片扩容而导致的内存重新分配和复制问题,使得函数更加高效。

这里留一个悬念:你们知道在什么情况下, if size < 0 这个看似不会成立的分支会成立吗?^_^ 欢迎在评论区发表你的见解。

零化处理

Go 1.22 版本中,对 DeleteDeleteFuncCompactCompactFuncReplace 函数进行了更新。这些函数的共同点是接受一个给定的切片参数,记为 s1,并返回一个新切片,记为 s2。被移除的元素会在 s1 中被置为零值(被移除的元素 是指从 s1 中移除的指定元素,在s2 中不存在)。

Delete 函数

通过不同 Go 版本的代码示例来感受 Delete 函数 零化处理 的更新。

  • Go 1.21 版本的代码示例

    go 复制代码
    package main
    
    import (
            "fmt"
            "slices"
    )
    
    func main() {
            s1 := []int{1, 2, 3, 4, 5}
            s2 := slices.Delete(s1, 3, 5)
            fmt.Println(s1)
            fmt.Println(s2)
    }

    代码运行结果如下所示:

    bash 复制代码
    [1 2 3 4 5]
    [1 2 3]
  • Go 1.22 版本的代码示例

    go 复制代码
    package main
    
    import (
            "fmt"
            "slices"
    )
    
    func main() {
            s1 := []int{1, 2, 3, 4, 5}
            s2 := slices.Delete(s1, 3, 5)
            fmt.Println(s1)
            fmt.Println(s2)
    }

    代码运行结果如下所示:

    bash 复制代码
    [1 2 3 0 0]
    [1 2 3]

通过对比不同版本的代码运行结果可知,被移除的元素 在原切片里被置为了 零值

DeleteFunc 函数

通过不同 Go 版本的代码示例来感受 DeleteFunc 函数 零化处理 的更新。

  • Go 1.21 版本的代码示例

    go 复制代码
    package main
    
    import (
            "fmt"
            "slices"
    )
    
    func main() {
            s1 := []int{1, 2, 3, 4, 5}
            s2 := slices.DeleteFunc(s1, func(e int) bool {
                    return e%2 == 0
            })
            fmt.Println(s1)
            fmt.Println(s2)
    }

    代码运行结果如下所示:

    bash 复制代码
    [1 3 5 4 5]
    [1 3 5]
  • Go 1.22 版本的代码示例

    go 复制代码
    package main
    
    import (
            "fmt"
            "slices"
    )
    
    func main() {
            s1 := []int{1, 2, 3, 4, 5}
            s2 := slices.DeleteFunc(s1, func(e int) bool {
                    return e%2 == 0
            })
            fmt.Println(s1)
            fmt.Println(s2)
    }

    代码运行结果如下所示:

    bash 复制代码
    [1 3 5 0 0]
    [1 3 5]

通过对比不同版本的代码运行结果可知,被移除的元素 在原切片里被置为了 零值

Compact 函数

通过不同 Go 版本的代码示例来感受 Compact 函数 零化处理 的更新。

  • Go 1.21 版本的代码示例

    go 复制代码
    package main
    
    import (
            "fmt"
            "slices"
    )
    
    func main() {
            s1 := []int{1, 2, 2, 3, 3, 4, 5}
            s2 := slices.Compact(s1)
            fmt.Println(s1)
            fmt.Println(s2)
    }

    代码运行结果如下所示:

    bash 复制代码
    [1 2 3 4 5 4 5]
    [1 2 3 4 5]
  • Go 1.22 版本的代码示例

    go 复制代码
    package main
    
    import (
            "fmt"
            "slices"
    )
    
    func main() {
            s1 := []int{1, 2, 2, 3, 3, 4, 5}
            s2 := slices.Compact(s1)
            fmt.Println(s1)
            fmt.Println(s2)
    }

    代码运行结果如下所示:

    bash 复制代码
    [1 2 3 4 5 0 0]
    [1 2 3 4 5]

通过对比不同版本的代码运行结果可知,被移除的元素 在原切片里被置为了 零值

CompactFunc 函数

通过不同 Go 版本的代码示例来感受 CompactFunc 函数 零化处理 的更新。

  • Go 1.21 版本的代码示例

    go 复制代码
    package main
    
    import (
            "fmt"
            "slices"
            "strings"
    )
    
    func main() {
            s1 := []string{"Gopher", "MingYong Chen", "mingyong chen"}
            s2 := slices.CompactFunc(s1, func(a, b string) bool {
                    return strings.ToLower(a) == strings.ToLower(b)
            })
            fmt.Printf("%#v\n", s1)
            fmt.Printf("%#v\n", s2)
    }

    代码运行结果如下所示:

    bash 复制代码
    []string{"Gopher", "MingYong Chen", "mingyong chen"}
    []string{"Gopher", "MingYong Chen"}
  • Go 1.22 版本的代码示例

    go 复制代码
    package main
    
    import (
            "fmt"
            "slices"
            "strings"
    )
    
    func main() {
            s1 := []string{"Gopher", "MingYong Chen", "mingyong chen"}
            s2 := slices.CompactFunc(s1, func(a, b string) bool {
                    return strings.ToLower(a) == strings.ToLower(b)
            })
            fmt.Printf("%#v\n", s1)
            fmt.Printf("%#v\n", s2)
    }

    代码运行结果如下所示:

    bash 复制代码
    []string{"Gopher", "MingYong Chen", ""}
    []string{"Gopher", "MingYong Chen"}

通过对比不同版本的代码运行结果可知,被移除的元素 在原切片里被置为了 零值

Replace 函数

通过不同 Go 版本的代码示例来感受 Replace 函数 零化处理 的更新。

  • Go 1.21 版本的代码示例

    go 复制代码
    package main
    
    import (
            "fmt"
            "slices"
    )
    
    func main() {
            s1 := []int{1, 6, 7, 4, 5}
            s2 := slices.Replace(s1, 1, 3, 2)
            fmt.Println(s1)
            fmt.Println(s2)
    }

    代码运行结果如下所示:

    bash 复制代码
    [1 2 4 5 5]
    [1 2 4 5]
  • Go 1.22 版本的代码示例

    go 复制代码
    package main
    
    import (
            "fmt"
            "slices"
    )
    
    func main() {
            s1 := []int{1, 6, 7, 4, 5}
            s2 := slices.Replace(s1, 1, 3, 2)
            fmt.Println(s1)
            fmt.Println(s2)
    }

    代码运行结果如下所示:

    bash 复制代码
    [1 2 4 5 0]
    [1 2 4 5]

在示例代码中,主要功能是将元素 2 替换原切片中 [1, 3] 区间的元素。因为一个元素替换了两个元素,所以第二个元素会被移除。通过对比不同版本的代码运行结果可知,被移除的元素 在原切片里被置为了 零值

越界插入优化

Go 1.22 版本对 slices 库的 Insert 函数进行了优化。在使用 Insert 函数时,若参数 i 超出切片的范围,总会触发 panic。而在 Go 1.22 版本之前,即使 i 越界了,在没有指定插入元素的情况下,该行为不会触发 panic

通过不同 Go 版本的代码示例来感受该优化。

  • Go 1.21 版本的代码示例

    go 复制代码
    package main
    
    import (
            "fmt"
            "slices"
    )
    
    func main() {
            s1 := []string{"程序员", "陈明勇"}
            s2 := slices.Insert(s1, 3)
            fmt.Println(s2)
    }

    代码运行结果如下所示:

    bash 复制代码
    [程序员 陈明勇]
  • Go 1.22 版本的代码示例

    go 复制代码
    package main
    
    import (
            "fmt"
            "slices"
    )
    
    func main() {
            s1 := []string{"程序员", "陈明勇"}
            s2 := slices.Insert(s1, 3)
            fmt.Println(s2)
    }

    代码运行结果如下所示:

    bash 复制代码
    panic: runtime error: slice bounds out of range [3:2]
    
    goroutine 1 [running]:
    slices.Insert[...]({0xc000068020?, 0xc000046740?, 0x0?}, 0x60?, {0x0?, 0xc000076058?, 0x524258?})
            /usr/local/go-faketime/src/slices/slices.go:133 +0x486
    main.main()
            /tmp/sandbox4036609674/prog.go:12 +0x68

在示例代码中,调用 slices.Insert 函数时,仅传递了切片 s 和插入位置索引 i 参数,而缺少了待插入元素值 v 参数。

通过对比不同版本的代码运行结果可知,在 Go 1.21 版本下,该代码正常运行结束而不会触发 panic,但在 Go 1.22 版本下,会触发 panic

小结

本文详细介绍了 Go 1.22 版本中 slices 库的更新内容,总结起来有三个方面:

  • 新增了 Concat 函数。
  • 对部分函数新增了零化处理的逻辑,包括 DeleteDeleteFuncCompactCompactFuncReplace 函数。
  • Insert 函数进行了越界插入优化。

推荐阅读

相关推荐
沈韶珺1 小时前
Visual Basic语言的云计算
开发语言·后端·golang
沈韶珺1 小时前
Perl语言的函数实现
开发语言·后端·golang
美味小鱼2 小时前
Rust 所有权特性详解
开发语言·后端·rust
我的K84092 小时前
Spring Boot基本项目结构
java·spring boot·后端
慕璃嫣3 小时前
Haskell语言的多线程编程
开发语言·后端·golang
晴空๓3 小时前
Spring Boot项目如何使用MyBatis实现分页查询
spring boot·后端·mybatis
Hello.Reader7 小时前
深入浅出 Rust 的强大 match 表达式
开发语言·后端·rust
customer0810 小时前
【开源免费】基于SpringBoot+Vue.JS体育馆管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
计算机-秋大田13 小时前
基于微信小程序的电子竞技信息交流平台设计与实现(LW+源码+讲解)
spring boot·后端·微信小程序·小程序·课程设计