go1.24 vs go1.18版本新特性

1、go新版本变化

  • go.mod文件中,

    go 1.22 这一行指定了你项目使用的 Go 语言的最低版本

    toolchain go1.x.x 确保项目在构建时始终使用指定的 Go 版本。这对于团队中的多个开发者,或者 CI/CD 环境来说,能够保证构建环境的一致性,避免不同的 Go 版本带来的潜在问题。如果你和团队成员或不同的构建环境在使用不同版本的 Go,使用 toolchain 可以统一所有环境的 Go 版本,确保一致性。

  • SwissTable 哈希表实现

    • 减少 2%-3% 的内存开销

    • 显著提高读写效率

    • 参考了 Rust 语言的实现方式

  • 自旋锁(Spin Mutex)引入

    • 为线程添加自旋状态

    • 降低线程切换开销,提升并发性能

  • 垃圾回收器(GC)改进

  • Golang1.21的package初始化顺序变更
    • 对所有import的包进行排序,放在一个列表中

    • 重复以下步骤,直到所有包都被初始化

      • 找到第一个所有依赖的import包都已经被初始化的包

      • 初始化该包,并从列表中移除

  • context包变动

    context包新增了一个WithCancelCause函数,与WithCancel不同,通过WithCancelCause返回的Context,我们可以得到cancel的原因,增加的还有WithDeadlineCause,WithTimeoutCause比如下面示例:

     myError := fmt.Errorf("%s", "myError")
     ctx, cancel := context.WithCancelCause(context.Background())
     cancel(myError)
     fmt.Println(ctx.Err())          // returns context.Canceled
     fmt.Println(context.Cause(ctx)) // returns myError

另外增加的还有WithoutCancel,它可以继承原 context 中的值(Value),不会被原 context 的取消(cancel)或超时(deadline)影响

    // 创建一个带超时的父 context
     parentCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
     defer cancel()
    
     // 从父 context 派生一个不会被取消的 context
     ctx := context.WithoutCancel(parentCtx)
    
     // 启动一个 goroutine 使用这个不会被取消的 context
     go func(ctx context.Context) {
     select {
     case <-time.After(3 * time.Second):
     fmt.Println("child goroutine finished (not canceled)")
     case <-ctx.Done():
     fmt.Println("child goroutine canceled:", ctx.Err())
     }
     }(ctx)
    
     // 等待 2 秒,父 context 已经超时
     time.Sleep(5 * time.Second)
     fmt.Println("main exit")
  • 支持mutiple errors
// 创建两个error
    err1 := errors.New("err1")
    err2 := errors.New("err2")
   
    // Join 函数将多个错误连接起来
    err3 := errors.Join(err1, err2)
    fmt.Printf("output err3:\n%v\n", err3)
    // 输出
    // err1
    // err2
   
    // errors.Is多个错误也能判断
    if errors.Is(err3, err1) && errors.Is(err3, err2) {
    fmt.Println("err3 contains err1 and err2")
    }
   
    err4 := fmt.Errorf("wrap error:%w \n", err3)
    fmt.Println(errors.Unwrap(err4))
  • time包变动
// time包新增格式常量,可以直接用了,不需要自己再定义了
     DateTime   = "2006-01-02 15:04:05"
     DateOnly   = "2006-01-02"
     TimeOnly   = "15:04:05"

2、由于泛型的引入,go自1.18后新增了很多标准库

Go 1.21 引入了以下标准库:

  • slices:提供泛型切片操作函数,如 slices.Sortslices.Clone 等。

  • maps:提供对 map 的通用操作函数,如 maps.Clonemaps.Equal 等。

  • cmp:提供泛型比较函数,如 cmp.Comparecmp.Less 等。

Go 1.22 新增了以下标准库:

  • math/rand/v2:引入了更高质量和更快的伪随机数生成算法,提供更清晰、一致的 API。

  • go/version:提供用于验证和比较 Go 版本字符串的函数。

Go 1.23 新增了以下标准库:

  • iter:提供用于迭代的泛型工具。

  • structs:structs.HostLayout用于声明和指定结构体采用主机的内存布局方式

  • unique:unique 会对所有被添加的值进行全局的并发安全缓存,以确保值的唯一性和有效重用。会做到运行时的驻留支持,以此达到开销较佳

slices 包示例

详细参考:slices/example_test.go

package main

import (
    "fmt"
    "slices"
    "strings"
)

func main() {
    // Clone: 创建切片的副本
    a := []int{1, 2, 3}
    b := slices.Clone(a)
    fmt.Println("Clone:", b) // 输出: [1 2 3]
  fmt.Printf("Origin:%p,Clone: %p\n", a, b) // 输出: 地址,Origin:0x14000418888,Clone: 0x140004188a0

    // Equal: 判断两个切片是否相等
    fmt.Println("Equal:", slices.Equal(a, b)) // 输出: true

    // Contains: 判断切片中是否包含某个元素
    fmt.Println("Contains 2:", slices.Contains(a, 2)) // 输出: true

  // BinarySearch: 查找元素位置
  names := []string{"Alice", "Bob", "Vera"}
    n, found := slices.BinarySearch(names, "Vera")
    fmt.Println("Vera:", n, found) // 输出: Vera: 2 true
  
    // Index: 获取元素在切片中的索引
    fmt.Println("Index of 3:", slices.Index(a, 3)) // 输出: 2

    // Insert: 在指定位置插入元素
    a = slices.Insert(a, 1, 99)
    fmt.Println("After Insert:", a) // 输出: [1 99 2 3]

    // Delete: 删除指定范围的元素
    a = slices.Delete(a, 1, 3)
    fmt.Println("After Delete:", a) // 输出: [1 3]

    // Sort: 对切片进行排序
    nums := []int{5, 2, 8, 1}
    slices.Sort(nums)
    fmt.Println("Sorted:", nums) // 输出: [1 2 5 8]

    // Compact: 移除相邻重复元素
    dup := []int{1, 1, 2, 2, 2, 3}
    n := slices.Compact(dup)
    fmt.Println("After Compact:", n) // 输出: [1 2 3]

    // Reverse: 反转切片
    names := []string{"Alice", "Bob", "Charlie"}
    slices.Reverse(names)
    fmt.Println("Reversed:", names) // 输出: [Charlie Bob Alice]

    // Replace: 替换指定范围的元素
    names = slices.Replace(names, 1, 2, "David", "Eve")
    fmt.Println("After Replace:", names) // 输出: [Charlie David Eve]

    // Repeat: 重复切片内容
    repeated := slices.Repeat([]int{1, 2}, 3)
    fmt.Println("Repeated:", repeated) // 输出: [1 2 1 2 1 2]

    // Min: 获取最小值
    minVal := slices.Min([]int{3, 1, 4, 2})
    fmt.Println("Min:", minVal) // 输出: 1

    // Max: 获取最大值
    maxVal := slices.Max([]int{3, 1, 4, 2})
    fmt.Println("Max:", maxVal) // 输出: 4

  // 拼接2个切片
  as1 := []int{0, 1, 2, 3}
    as2 := []int{4, 5, 6}
    concat := slices.Concat(as1, as2)
    fmt.Println("concat:", concat) // 输出: [0 1 2 3 4 5 6]
  
    // SortFunc: 使用自定义比较函数排序
    strs := []string{"banana", "apple", "cherry"}
    slices.SortFunc(strs, func(a, b string) int {
        return strings.Compare(a, b)
    })
    fmt.Println("Custom Sorted:", strs) // 输出: [apple banana cherry]

    // SortStableFunc: 稳定排序,保持相等元素的原始顺序
  // 对比:sort.Slice:适用于需要不稳定排序的情况,性能可能更优。如果你的需求不关心相等元素的顺序,可以继续使用 sort.Slice。如果需要稳定排序,那么 slices.SortStableFunc 会是一个更合适的选择
    type Person struct {
        Name string
        Age  int
    }
    people := []Person{
        {"Alice", 30},
        {"Bob", 25},
        {"Alice", 22},
    }
    slices.SortStableFunc(people, func(a, b Person) int {
        return strings.Compare(a.Name, b.Name)
    })
    fmt.Println("Stable Sorted:", people)
    // 输出: [{Alice 30} {Alice 22} {Bob 25}]
  
  // Chunk:对切片分批
  type Person struct {
        Name string
        Age  int
    }
    type People []Person
    people := People{
        {"Gopher", 13},
        {"Alice", 20},
        {"Bob", 5},
        {"Vera", 24},
        {"Zac", 15},
    }
    // Chunk people into []Person 2 elements at a time.
    for c := range slices.Chunk(people, 2) {
        fmt.Println(c)
    }
    // Output:
    // [{Gopher 13} {Alice 20}]
    // [{Bob 5} {Vera 24}]
    // [{Zac 15}]
}

// maps示例

详细参考:maps/example_test.go

package main

import (
    "fmt"
    "maps"
    "slices"
)

func main() {
  m1 := map[string]int{"a": 1, "b": 2}
    m2 := maps.Clone(m1)
    fmt.Println("Clone:", m2)                   // 输出: map[a:1 b:2]
    fmt.Printf("Origin:%p,Clone: %p\n", m1, m2) // 输出地址:不同,Origin:0x14000a01110,Clone: 0x14000a01140

    // Equal: 判断两个 map 是否相等
    m3 := map[string]int{"b": 2, "a": 1}
    fmt.Println("Equal:", maps.Equal(m1, m3)) // 输出: true

    // Keys: 获取 map 的所有键
    keys := slices.Sorted(maps.Keys(m1))
    fmt.Println("Keys:", keys) // 输出: [a b]

    // Values: 获取 map 的所有值
    values := slices.Sorted(maps.Values(m1))
    fmt.Println("Values:", values) // 输出: [1 2]

    // Copy: 将 src 的键值对复制到 dst 中
    dst := map[string]int{"a": 10}
    src := map[string]int{"b": 20}
    maps.Copy(dst, src)
    fmt.Println("After Copy:", dst) // 输出: map[a:10 b:20]

    // DeleteFunc: 根据条件删除键值对
    maps.DeleteFunc(dst, func(k string, v int) bool {
        return v > 15
    })
    fmt.Println("After DeleteFunc:", dst) // 输出: map[a:10]

    // Collect collects key-value pairs from seq into a new map
    s1 := []string{"zero", "one", "two", "three"}
    res := maps.Collect(slices.All(s1))
    fmt.Println("collect res is:", res) // 输出: map[0:zero 1:one 2:two 3:three]
}

// cmp示例

package main

import (
    "cmp"
    "fmt"
)

func main() {
    a, b := 3, 7
    result := cmp.Compare(a, b)
    fmt.Println("Compare(3, 7):", result) // 输出: -1,因为 3 < 7

    // 比较两个字符串
    x, y := "apple", "banana"
    fmt.Println("Compare(apple, banana):", cmp.Compare(x, y)) // 输出: -1

    fmt.Println("Less(3, 7):", cmp.Less(3, 7)) // true

    // cmp.Or返回第一个不是零值的结果
    // string 类型的零值是 ""
    name := ""
    defaultName := "Guest"
    finalName := cmp.Or(name, defaultName)
    fmt.Println("Final Name:", finalName) // 输出: "Guest"

    // int 类型的零值是 0
    count := 0
    defaultCount := 10
    finalCount := cmp.Or(count, defaultCount)
    fmt.Println("Final Count:", finalCount) // 输出: 10

    // 如果 v 不是零值,返回自身
    nonZero := "Alice"
    fmt.Println("Or(\"Alice\", \"Guest\"):", cmp.Or(nonZero, defaultName)) // 输出: "Alice"
}

rand示例

自动使用全局安全种子(不需要再手动调用 rand.Seed()

package main

import (
    "fmt"
    "math/rand/v2"
)

func main() {
    // 比较两个整数
    // 生成 [0, 100) 范围的随机整数
    n := rand.IntN(100)
    fmt.Println("随机整数 [0,100):", n)

    fmt.Println(rand.IntN(100))
    fmt.Println(rand.IntN(100))
    fmt.Println(rand.IntN(100))

    // 生成 [0.0, 1.0) 的随机 float64
    f := rand.Float64()
    fmt.Println("随机 float64:", f)

    s := []string{"apple", "banana", "cherry", "date"}
    rand.Shuffle(len(s), func(i, j int) {
        s[i], s[j] = s[j], s[i]
    })
    fmt.Println("打乱后的切片:", s)
}

3、使用变化

for...range...变化(Go1.22 )

// 1、for...range支持整数范围输出,不再需要像以前for i:=0;i<10;i++这样写了
package main

import "fmt"

func main() {
    for i := range 10 {
        // fmt.Println(10 - i)  // 输出1到10
        fmt.Println(i)          // 输出0到9
    }
    fmt.Println("go1.22 has lift-off!")
}

// "for "循环在迭代之间意外共享循环变量的问题现已得到解决。以下代码将按一定顺序打印 "a"、"b "和 "c",
// 在以前go版本这样写输出的结果是"c","c","c"
func main() {
   values := []string{"a", "b", "c"}
  
  for _, val := range values {
    go func() {
      fmt.Println(val)
   }()
 }
 time.Sleep(time.Second * 3)
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容