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

简介: 本文详细介绍了 Go 1.22 版本中 slices 库的更新内容,总结起来有三个方面:新增了 Concat 函数、对部分函数新增了零化处理的逻辑和对 Insert 函数进行了越界插入优化

前言

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

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

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

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

Go-Let'sGo.jpg

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

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

代码示例:

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)
}

代码运行结果如下所示:

cap: 6, len: 6
[Go slices Go maps Go strings Go strconv 程序员 陈明勇]

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

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

// 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 版本的代码示例

    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)
    }
    

    代码运行结果如下所示:

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

    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)
    }
    

    代码运行结果如下所示:

    [1 2 3 0 0]
    [1 2 3]
    

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

DeleteFunc 函数

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

  • Go 1.21 版本的代码示例

    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)
    }
    

    代码运行结果如下所示:

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

    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)
    }
    

    代码运行结果如下所示:

    [1 3 5 0 0]
    [1 3 5]
    

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

Compact 函数

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

  • Go 1.21 版本的代码示例

    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)
    }
    

    代码运行结果如下所示:

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

    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)
    }
    

    代码运行结果如下所示:

    [1 2 3 4 5 0 0]
    [1 2 3 4 5]
    

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

CompactFunc 函数

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

  • Go 1.21 版本的代码示例

    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)
    }
    

    代码运行结果如下所示:

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

    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)
    }
    

    代码运行结果如下所示:

    []string{
         "Gopher", "MingYong Chen", ""}
    []string{
         "Gopher", "MingYong Chen"}
    

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

Replace 函数

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

  • Go 1.21 版本的代码示例

    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)
    }
    

    代码运行结果如下所示:

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

    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)
    }
    

    代码运行结果如下所示:

    [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 版本的代码示例

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

    代码运行结果如下所示:

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

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

    代码运行结果如下所示:

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

推荐阅读

目录
相关文章
|
2月前
|
数据采集 存储 Go
使用Go语言和chromedp库下载Instagram图片:简易指南
Go语言爬虫示例使用chromedp库下载Instagram图片,关键步骤包括设置代理IP、创建带代理的浏览器上下文及执行任务,如导航至用户页面、截图并存储图片。代码中新增`analyzeAndStoreImage`函数对图片进行分析和分类后存储。注意Instagram的反爬策略可能需要代码适时调整。
使用Go语言和chromedp库下载Instagram图片:简易指南
|
25天前
|
NoSQL Go Redis
如何使用 Go 和 `go-redis/redis` 库连接到 Redis 并执行一些基本操作
如何使用 Go 和 `go-redis/redis` 库连接到 Redis 并执行一些基本操作
16 1
|
1月前
|
NoSQL 安全 Go
Go 语言 mongox 库:简化操作、安全、高效、可扩展、BSON 构建
go mongox 是一个基于泛型的库,扩展了 MongoDB 的官方库。通过泛型技术,它实现了结构体与 MongoDB 集合的绑定,旨在提供类型安全和简化的数据操作。 go mongox 还引入链式调用,让文档操作更流畅,并且提供了丰富的 BSON 构建器和内置函数,简化了 BSON 数据的构建。 此外,它还支持插件化编程和内置多种钩子函数,为数据库操作前后的自定义逻辑提供灵活性,增强了应用的可扩展性和可维护性。
67 6
|
2月前
|
Java 测试技术 Go
使用go的内置运行时库调试和优化程序
【5月更文挑战第18天】在本文中,作者探讨了如何为运行时程序添加剖析支持以优化性能。他们面临的问题是一个程序需要找出平方根为整数且逆序平方等于其逆序的数字。他们首先展示了原始代码,然后使用`runtime`库进行优化,将计算和调用功能分离,同时记录CPU和内存使用情况。
41 4
|
2月前
|
JSON Java Go
使用go语言中的内置库调试性能
【5月更文挑战第21天】本文介绍Go 语言提供了内置的 expvar 模块来输出度量数据,帮助定位性能瓶颈。与 pprof 不同,expvar 专注于应用的宏观状态,通过 HTTP 接口 `/debug/vars` 提供标准的 JSON 格式数据,包括自定义度量和内存统计等。通过 expvar,开发者可以轻松监控应用状态,如消息处理速率、内存使用等,而无需像 C++ 或 Java 那样手动实现。
35 0
使用go语言中的内置库调试性能
|
2月前
|
SQL 开发框架 .NET
你确定不学?Go标准库之 text/template
你确定不学?Go标准库之 text/template
24 2
|
2月前
|
运维 监控 Go
Golang深入浅出之-Go语言中的日志记录:log与logrus库
【4月更文挑战第27天】本文比较了Go语言中标准库`log`与第三方库`logrus`的日志功能。`log`简单但不支持日志级别配置和多样化格式,而`logrus`提供更丰富的功能,如日志级别控制、自定义格式和钩子。文章指出了使用`logrus`时可能遇到的问题,如全局logger滥用、日志级别设置不当和过度依赖字段,并给出了避免错误的建议,强调理解日志级别、合理利用结构化日志、模块化日志管理和定期审查日志配置的重要性。通过这些实践,开发者能提高应用监控和故障排查能力。
125 1
|
2月前
|
中间件 Go API
Golang深入浅出之-Go语言标准库net/http:构建Web服务器
【4月更文挑战第25天】Go语言的`net/http`包是构建高性能Web服务器的核心,提供创建服务器和发起请求的功能。本文讨论了使用中的常见问题和解决方案,包括:使用第三方路由库改进路由设计、引入中间件处理通用逻辑、设置合适的超时和连接管理以防止资源泄露。通过基础服务器和中间件的代码示例,展示了如何有效运用`net/http`包。掌握这些最佳实践,有助于开发出高效、易维护的Web服务。
56 1
|
2月前
|
NoSQL Shell Go
在go中简单使用go-redis库
在go中简单使用go-redis库
|
2月前
|
监控 安全 Go
【Go语言专栏】Go语言中的并发性能分析与优化
【4月更文挑战第30天】Go语言以其卓越的并发性能和简洁语法著称,通过goroutines和channels实现并发。并发性能分析旨在解决竞态条件、死锁和资源争用等问题,以提升多核环境下的程序效率。使用pprof等工具可检测性能瓶颈,优化策略包括减少锁范围、使用无锁数据结构、控制goroutines数量、应用worker pool和优化channel使用。理解并发模型和合理利用并发原语是编写高效并发代码的关键。