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 函数进行了越界插入优化。

推荐阅读

相关推荐
SimonKing9 分钟前
无需重启!动态修改日志级别的神技,运维开发都哭了
java·后端·程序员
架构精进之路28 分钟前
多智能体系统不是银弹
后端·架构·aigc
涡能增压发动积1 小时前
MySQL数据库为何逐渐黯淡,PostgreSQL为何能新王登基
人工智能·后端
架构精进之路1 小时前
多智能体系统架构解析
后端·架构·ai编程
Java中文社群1 小时前
重磅!Ollama发布UI界面,告别命令窗口!
java·人工智能·后端
程序员清风2 小时前
程序员代码有Bug别怕,人生亦是如此!
java·后端·面试
就是帅我不改2 小时前
告别996!高可用低耦合架构揭秘:SpringBoot + RabbitMQ 让订单系统不再崩
java·后端·面试
用户6120414922132 小时前
C语言做的区块链模拟系统(极简版)
c语言·后端·敏捷开发
Mintopia2 小时前
🎬《Next 全栈 CRUD 的百老汇》
前端·后端·next.js
CF14年老兵3 小时前
深入浅出 Python 一等函数:一份友好的全面解析
后端·python·trae