← 返回首页

基础语法

变量声明

Go 语言有多种声明变量的方式:

package main

import "fmt"

func main() {
    // 方式1:使用 var 关键字
    var name string = "Go"
    var age int = 10

    // 方式2:类型推断
    var language = "Golang"

    // 方式3:短变量声明(最常用)
    version := "1.21"

    // 方式4:声明多个变量
    var (
        x int = 1
        y int = 2
    )

    fmt.Println(name, age, language, version, x, y)
}

基本数据类型

数值类型

package main

import "fmt"

func main() {
    // 整型
    var i int = 42
    var i8 int8 = 127
    var i16 int16 = 32767
    var i32 int32 = 2147483647
    var i64 int64 = 9223372036854775807

    // 无符号整型
    var u uint = 42
    var u8 uint8 = 255
    var u16 uint16 = 65535
    var u32 uint32 = 4294967295
    var u64 uint64 = 18446744073709551615

    // 浮点型
    var f32 float32 = 3.14
    var f64 float64 = 3.141592653589793

    // 复数
    var c64 complex64 = 1 + 2i
    var c128 complex128 = 1 + 2i

    fmt.Println(i, i8, i16, i32, i64)
    fmt.Println(u, u8, u16, u32, u64)
    fmt.Println(f32, f64)
    fmt.Println(c64, c128)
}

字符串和布尔类型

package main

import "fmt"

func main() {
    // 字符串
    var str string = "Hello, Go!"
    message := "Welcome to Go programming"

    // 多行字符串
    multiline := `这是一个
多行字符串
示例`

    // 布尔类型
    var isTrue bool = true
    isFalse := false

    fmt.Println(str)
    fmt.Println(message)
    fmt.Println(multiline)
    fmt.Println(isTrue, isFalse)
}

常量

常量使用 const 关键字声明,值在编译时确定且不可修改。

package main

import "fmt"

func main() {
    // 单个常量
    const PI = 3.14159
    const MaxConnections = 100

    // 常量组
    const (
        StatusOK       = 200
        StatusNotFound = 404
        StatusError    = 500
    )

    fmt.Println(PI, MaxConnections)
    fmt.Println(StatusOK, StatusNotFound, StatusError)
}

运算符

算术运算符

package main

import "fmt"

func main() {
    a, b := 10, 3

    fmt.Println("加法:", a + b)    // 13
    fmt.Println("减法:", a - b)    // 7
    fmt.Println("乘法:", a * b)    // 30
    fmt.Println("除法:", a / b)    // 3
    fmt.Println("取余:", a % b)    // 1
}

比较运算符

package main

import "fmt"

func main() {
    a, b := 10, 20

    fmt.Println("等于:", a == b)  // false
    fmt.Println("不等于:", a != b)  // true
    fmt.Println("小于:", a < b)   // true
    fmt.Println("小于等于:", a <= b) // true
    fmt.Println("大于:", a > b)   // false
    fmt.Println("大于等于:", a >= b) // false
}

逻辑运算符

package main

import "fmt"

func main() {
    a, b := true, false

    fmt.Println("与:", a && b)  // false
    fmt.Println("或:", a || b)  // true
    fmt.Println("非:", !a)     // false
}

字符串操作

package main

import (
    "fmt"
    "strings"
)

func main() {
    str := "Hello, Go Programming!"

    // 字符串长度
    fmt.Println("长度:", len(str))

    // 字符串拼接
    str2 := str + " Welcome!"
    fmt.Println("拼接:", str2)

    // 字符串分割
    parts := strings.Split(str, ", ")
    fmt.Println("分割:", parts)

    // 字符串包含
    fmt.Println("包含 Go:", strings.Contains(str, "Go"))

    // 字符串替换
    replaced := strings.Replace(str, "Go", "Golang", 1)
    fmt.Println("替换:", replaced)

    // 大小写转换
    fmt.Println("大写:", strings.ToUpper(str))
    fmt.Println("小写:", strings.ToLower(str))

    // 去除空格
    str3 := "  Hello  "
    fmt.Println("去除空格:", strings.Trim(str3, " "))
}

数组

package main

import "fmt"

func main() {
    // 声明数组
    var arr1 [5]int
    arr1[0] = 1
    arr1[1] = 2

    // 数组初始化
    arr2 := [5]int{1, 2, 3, 4, 5

    // 让编译器计算长度
    arr3 := [...]int{1, 2, 3}

    fmt.Println("数组1:", arr1)
    fmt.Println("数组2:", arr2)
    fmt.Println("数组3:", arr3)
    fmt.Println("数组2长度:", len(arr2))
}

切片(Slice)

package main

import "fmt"

func main() {
    // 创建切片
    slice1 := []int{1, 2, 3, 4, 5

    // 使用 make 创建切片
    slice2 := make([]int, 3, 5)

    // 切片操作
    fmt.Println("完整切片:", slice1)
    fmt.Println("子切片 [1:3]:", slice1[1:3])
    fmt.Println("子切片 [:3]:", slice1[:3])
    fmt.Println("子切片 [2:]:", slice1[2:])

    // 添加元素
    slice1 = append(slice1, 6, 7)
    fmt.Println("追加后:", slice1)

    // 切片长度和容量
    fmt.Println("长度:", len(slice1))
    fmt.Println("容量:", cap(slice1))
}

切片的内部结构

切片在 Go 内部是一个结构体,包含三个字段:

type sliceHeader struct {
    ptr unsafe.Pointer  // 指向底层数组的指针
    len int              // 切片长度(可访问的元素数量)
    cap int              // 切片容量(底层数组从 ptr 开始的总大小)
}

切片是对底层数组的引用,多个切片可以共享同一个底层数组。修改一个切片会影响其他共享底层数组的切片。

切片的实现原理

1. 切片的创建:

// 字面量创建
s := []int{1, 2, 3}  // 创建切片和底层数组

// make 创建
s := make([]int, 3, 5)  // len=3, cap=5

// 从数组/切片创建
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4]  // 共享 arr 的底层数组

2. 切片的扩容机制:

当使用 append 添加元素且容量不足时,Go 会自动扩容。扩容策略如下:

package main

import "fmt"

func main() {
    s := []int{1}
    fmt.Printf("len=%d, cap=%d
", len(s), cap(s))  // len=1, cap=1

    s = append(s, 2)
    fmt.Printf("len=%d, cap=%d
", len(s), cap(s))  // len=2, cap=2

    s = append(s, 3)
    fmt.Printf("len=%d, cap=%d
", len(s), cap(s))  // len=3, cap=4 (扩容)

    s = append(s, 4, 5)
    fmt.Printf("len=%d, cap=%d
", len(s), cap(s))  // len=5, cap=8 (扩容)
}

扩容规则(Go 1.20+):

  • 新容量 ≥ 2×旧容量
  • 新容量 ≥ 旧容量 + 新元素数量
  • 对于大切片,扩容因子约为 1.25

3. 切片的共享问题:

package main

import "fmt"

func main() {
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := slice1[1:3]  // [2, 3]

    // 修改 slice2 会影响 slice1
    slice2[0] = 99
    fmt.Println("slice1:", slice1)  // [1, 99, 3, 4, 5]
    fmt.Println("slice2:", slice2)  // [99, 3]
}

切片最佳实践

1. 预分配容量:

// 不推荐:频繁扩容
s := []int{}
for i := 0; i < 1000; i++ {
    s = append(s, i)  // 多次扩容,性能差
}

// 推荐:预分配容量
s := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
    s = append(s, i)  // 无需扩容,性能好
}

2. 避免大内存泄漏:

func processLargeData() {
    // 假设 data 是一个很大的切片
    data := make([]byte, 1000000)
    
    // 只需要前 100 个字节
    header := data[:100]
    
    // 问题:header 仍然引用整个 data,导致 data 无法被 GC
    
    // 解决方案:复制需要的数据
    header = make([]byte, 100)
    copy(header, data[:100])
}

3. 使用 copy 而不是 append:

// 不推荐
result := []int{}
for _, v := range source {
    result = append(result, v)  // 可能多次扩容
}

// 推荐
result := make([]int, 0, len(source))
for _, v := range source {
    result = append(result, v)
}

// 或者直接使用 copy
result := make([]int, len(source))
copy(result, source)

4. 切片作为函数参数:

// 切片是引用传递,但 len 和 cap 是值传递
func modifySlice(s []int) {
    // 修改元素会影响原切片
    s[0] = 999
    
    // append 不会影响原切片(除非发生扩容)
    s = append(s, 100)
}

// 如果需要修改原切片,返回新的切片
func appendSlice(s []int, values ...int) []int {
    return append(s, values...)
}

5. 检查切片是否为空:

// 不推荐
if s != nil {  // nil 切片和空切片不同
    // 处理
}

// 推荐
if len(s) > 0 {  // 检查长度
    // 处理
}

6. 使用 range 遍历切片:

// range 返回索引和值的副本
for i, v := range slice {
    // v 是副本,修改 v 不会影响原切片
}

// 如果需要修改元素,使用索引
for i := range slice {
    slice[i] = newValue
}

7. 切片截取的内存共享陷阱:

func getFirstN(data []byte, n int) []byte {
    // 不推荐:返回的切片仍然引用整个 data
    return data[:n]
    
    // 推荐:复制数据,避免内存泄漏
    result := make([]byte, n)
    copy(result, data[:n])
    return result
}

映射(Map)

package main

import "fmt"

func main() {
    // 创建 map
    m1 := make(map[string]int)
    m1["apple"] = 5
    m1["banana"] = 3

    // map 字面量
    m2 := map[string]int{
        "red":    255,
        "green":  128,
        "blue":   64,
    }

    // 读取值
    value, exists := m1["apple"]
    fmt.Println("apple 的值:", value, "存在:", exists)

    // 删除键
    delete(m1, "banana")

    // 遍历 map
    for key, val := range m2 {
        fmt.Println(key, ":", val)
    }

    fmt.Println("map 长度:", len(m2))
}

Map 的内部结构

Map 在 Go 内部使用哈希表实现,主要包含以下结构:

type hmap struct {
    count     int         // map 中元素的数量
    flags     uint8       // 状态标志
    B         uint8       // 桶数组大小的对数(bucket 数组大小 = 2^B)
    noverflow uint16     // 溢出的桶数量
    hash0     uint32     // 哈希种子
    buckets   unsafe.Pointer // 桶数组的指针
    oldbuckets unsafe.Pointer // 扩容时的旧桶数组
    nevacuate uintptr    // 扩容搬迁进度
    extra *mapextra // 额外信息(溢出桶等)
}

Map 的查找、插入和删除操作的平均时间复杂度为 O(1)。当 map 中的元素数量超过负载因子时,会自动扩容。

Map 的实现原理

1. 哈希计算:

Go 使用哈希函数计算 key 的哈希值,然后根据哈希值确定元素在桶数组中的位置。

// 哈希计算示例
// 1. 计算 key 的哈希值
hash := hashFunction(key, hmap.hash0)

// 2. 取哈希值的低 B 位确定桶索引
// 例如:B=3 时,bucket 数组大小为 8
bucketIndex := hash & (uintptr(1)<1)

// 3. 取哈希值的高 8 位作为 tophash
top := uint8(hash >> (unsafe.Sizeof(hash)*8 - 8))

不同类型的 key 使用不同的哈希算法:

  • 整数:直接使用整数值作为哈希
  • 字符串:使用 FNV-1a 算法
  • 指针:使用指针地址
  • 结构体:递归计算各字段的哈希

2. 桶(Bucket)结构详解:

每个桶可以存储最多 8 个键值对(bucketSize = 8)。桶的内存布局经过优化:

type bmap struct {
    // tophash 数组:存储每个键的哈希值高 8 位
    // 用于快速判断键是否可能存在于桶中
    tophash [bucketSize]uint8
    
    // keys 和 values 数组交错存储,减少填充
    // 实际内存布局:[tophash0][tophash1]...[key0][key1]...[val0][val1]...
    keys   [bucketSize]keyType
    values [bucketSize]valueType
    
    // overflow 指向溢出桶链表
    // 当桶满时,新元素存储在溢出桶中
    overflow *bmap
}

3. 查找过程详解:

package main

import "fmt"

func main() {
    m := make(map[string]int)
    m["apple"] = 5
    m["banana"] = 3
    m["cherry"] = 7

    // 查找 "banana" 的详细过程:
    
    // 步骤 1:计算 "banana" 的哈希值
    hash := hashFunction("banana", hash0)
    fmt.Printf("哈希值: %d
", hash)
    
    // 步骤 2:计算桶索引
    bucketIndex := hash & (bucketMask)
    fmt.Printf("桶索引: %d
", bucketIndex)
    
    // 步骤 3:在桶中查找
    bucket := buckets[bucketIndex]
    for i := 0; i < bucketSize; i++ {
        // 先检查 tophash 快速过滤
        if bucket.tophash[i] != top {
            continue
        }
        
        // 再比较完整的 key
        if bucket.keys[i] == "banana" {
            // 找到匹配,返回值
            return bucket.values[i]
        }
    }
    
    // 步骤 4:检查溢出桶链表
    for bucket = bucket.overflow; bucket != nil; bucket = bucket.overflow {
        // 在溢出桶中重复步骤 3
        // ...
    }
    
    // 步骤 5:未找到,返回零值
    return zeroValue
}

4. 插入过程详解:

package main

import "fmt"

func main() {
    m := make(map[string]int)
    
    // 插入 "orange" 的过程:
    
    // 步骤 1:计算哈希和桶索引
    hash := hashFunction("orange", hash0)
    bucketIndex := hash & bucketMask
    top := uint8(hash >> (unsafe.Sizeof(hash)*8 - 8))
    
    // 步骤 2:在桶中查找空位
    bucket := buckets[bucketIndex]
    var insertPos int = -1
    
    for i := 0; i < bucketSize; i++ {
        if bucket.tophash[i] == empty {
            // 找到空位
            insertPos = i
            break
        }
    }
    
    if insertPos != -1 {
        // 桶中有空位,直接插入
        bucket.tophash[insertPos] = top
        bucket.keys[insertPos] = "orange"
        bucket.values[insertPos] = 10
    } else {
        // 桶已满,创建溢出桶
        if bucket.overflow == nil {
            bucket.overflow = newbmap()
        }
        // 在溢出桶中插入
        insertIntoOverflow(bucket.overflow, "orange", 10)
    }
    
    // 步骤 3:检查是否需要扩容
    if hmap.noverflow >= float64(hmap.count)/loadFactor {
        grow(hmap)
    }
}

5. 删除过程详解:

package main

import "fmt"

func main() {
    m := make(map[string]int)
    m["apple"] = 5
    
    // 删除 "apple" 的过程:
    
    // 步骤 1:定位 key 的位置
    hash := hashFunction("apple", hash0)
    bucketIndex := hash & bucketMask
    bucket := buckets[bucketIndex]
    
    for i := 0; i < bucketSize; i++ {
        if bucket.tophash[i] != top {
            continue
        }
        if bucket.keys[i] == "apple" {
            // 找到 key,标记为已删除
            bucket.tophash[i] = emptyOne  // 特殊标记:已删除
            bucket.keys[i] = zeroKey
            bucket.values[i] = zeroValue
            hmap.count--
            break
        }
    }
    
    // 注意:删除操作不会立即回收空间
    // 已删除的位置会被 emptyOne 标记
    // 后续插入时可以复用这些位置
}

6. 扩容机制详解:

Go 的 map 有两种扩容方式:

等量扩容(rehash):

  • 触发条件:溢出桶过多(noverflow >= 1 << (B & 15))
  • 目的:减少溢出桶,提高查找效率
  • 过程:重新分配相同大小的桶数组,重新哈希所有元素

增量扩容(grow):

  • 触发条件:元素数量超过负载因子(count / bucketSize > loadFactor)
  • 负载因子通常为 6.5
  • 目的:增加桶数量,减少哈希冲突
  • 过程:桶数组大小翻倍(B++),渐进式搬迁元素
package main

import "fmt"

func main() {
    m := make(map[int]int)

    // 观察扩容过程
    for i := 0; i < 100; i++ {
        beforeB := m.B
        m[i] = i * i
        afterB := m.B
        
        if beforeB != afterB {
            fmt.Printf("扩容: B 从 %d 变为 %d
", beforeB, afterB)
        }
    }
}

7. 渐进式扩容详解:

增量扩容时,Go 采用渐进式搬迁策略:

type hmap struct {
    // ... 其他字段
    
    oldbuckets unsafe.Pointer  // 旧桶数组
    nevacuate  uintptr       // 搬迁进度:已搬迁的桶数量
    noldbuckets uintptr     // 旧桶数组大小
}

// 搬迁过程
func evacuate(hmap *hmap) {
    // 每次操作最多搬迁 2 个桶
    for i := 0; i < 2; i++ {
        // 搬迁旧桶数组的第 nevacuate 个桶
        oldbucket := (*bmap)(add(hmap.oldbuckets, 
            uintptr(hmap.nevacuate)*bucketSize))
        
        // 将桶中的所有元素重新哈希到新桶数组
        evacuateOne(oldbucket, hmap)
        
        hmap.nevacuate++
        
        // 搬迁完成,释放旧桶数组
        if hmap.nevacuate == hmap.noldbuckets {
            hmap.oldbuckets = nil
        }
    }
}

渐进式扩容的优势:

  • 避免单次操作的性能抖动
  • 分散扩容开销到多次操作
  • 提高系统的响应性

8. 溢出桶链表:

当桶满时,新元素存储在溢出桶中,形成链表:

// 桶结构
Bucket {
    tophash: [uint8]  // [top1, top2, top3, top4, top5, top6, top7, top8]
    keys:    [KeyType]  // [key1, key2, key3, key4, key5, key6, key7, key8]
    values:  [ValueType] // [val1, val2, val3, val4, val5, val6, val7, val8]
    overflow: *Bucket    // -> 溢出桶1 -> 溢出桶2 -> ...
}

// 查找时的遍历顺序:
// 1. 主桶
// 2. 溢出桶1
// 3. 溢出桶2
// ...

9. 空位标记:

Go 使用特殊的 tophash 值标记空位:

const (
    emptyRest = 0      // 空位,后面没有元素
    emptyOne  = 1      // 已删除的位置
    minTopHash = 4     // 最小有效的 tophash
)

// 查找时:
for i := 0; i < bucketSize; i++ {
    if tophash[i] == emptyRest {
        break  // 后面没有元素了
    }
    if tophash[i] == emptyOne {
        continue  // 跳过已删除的位置
    }
    // 检查实际的键
}

10. 内存布局优化:

Go 对 map 的内存布局进行了优化:

  • 减少填充:keys 和 values 数组交错存储
  • 缓存友好:tophash 数组在前面,便于快速过滤
  • 内存对齐:根据类型大小自动对齐
// 实际内存布局(64位系统)
// [8]byte tophash
// [8]KeyType keys
// [8]ValueType values
// 指针 overflow

// 假设 KeyType=int, ValueType=int
// tophash: 8 bytes
// keys: 8 * 8 = 64 bytes
// values: 8 * 8 = 64 bytes
// 总计: 8 + 64 + 64 = 136 bytes

Map 最佳实践

1. 预分配 map 容量:

// 不推荐:频繁扩容
m := make(map[string]int)
for i := 0; i < 1000; i++ {
    m[fmt.Sprintf("key%d", i)] = i  // 多次扩容
}

// 推荐:预分配容量
m := make(map[string]int, 1000)
for i := 0; i < 1000; i++ {
    m[fmt.Sprintf("key%d", i)] = i  // 无需扩容
}

2. 检查键是否存在:

// 推荐:使用两个返回值
if value, ok := m["key"]; ok {
    fmt.Println("键存在,值为:", value)
} else {
    fmt.Println("键不存在")
}

3. 遍历 map 的顺序:

// 注意:map 的遍历顺序是随机的
for key, value := range m {
    fmt.Println(key, value)
}

// 如果需要有序遍历,先收集键并排序
keys := make([]string, 0, len(m))
for key := range m {
    keys = append(keys, key)
}
// 排序键
for _, key := range keys {
    fmt.Println(key, m[key])
}

4. 并发安全:

// 不推荐:map 不是并发安全的
var m = make(map[int]int)
go func() {
    m[1] = 1  // 可能导致 panic
}()

// 推荐:使用 sync.Map 或加锁
import "sync"

// 方式1:使用 sync.Map(适合读多写少)
var m sync.Map
m.Store(1, 1)
if value, ok := m.Load(1); ok {
    fmt.Println(value)
}

// 方式2:使用互斥锁(适合读写均衡)
type SafeMap struct {
    mu sync.RWMutex
    m  map[int]int
}

func (sm *SafeMap) Get(key int) (int, bool) {
    sm.mu.RLock()
    defer sm.mu.RUnlock()
    v, ok := sm.m[key]
    return v, ok
}

func (sm *SafeMap) Set(key, value int) {
    sm.mu.Lock()
    defer sm.mu.Unlock()
    sm.m[key] = value
}

5. 删除键:

// 删除键
delete(m, "key")

// 删除不存在的键是安全的,不会 panic
delete(m, "nonexistent")

6. map 作为函数参数:

// map 是引用类型,传递的是指针
func modifyMap(m map[string]int) {
    m["new"] = 100  // 会影响原 map
    delete(m, "old")   // 会影响原 map
}

// 如果 map 可能为 nil,需要检查
func safeSet(m map[string]int, key string, value int) {
    if m == nil {
        m = make(map[string]int)
    }
    m[key] = value
}

7. 选择合适的 key 类型:

// 推荐:可比较的类型作为 key
m1 := make(map[string]int)    // 字符串
m2 := make(map[int]int)        // 整数
m3 := make(map[bool]int)       // 布尔值
m4 := make(map[float64]int)    // 浮点数(不推荐,精度问题)

// 不推荐:不可比较的类型(slice、map、func)
// m := make(map[[]int]int)  // 编译错误

// 如果需要使用复合类型作为 key,确保它是可比较的
type Point struct {
    X, Y int
}
m5 := make(map[Point]string)

8. 避免 map 的内存泄漏:

// 清空 map
for key := range m {
    delete(m, key)
}
// 或者直接创建新的 map
m = make(map[string]int)

// 注意:如果 map 中存储了大量数据,即使清空后,
// 底层数组可能不会被立即释放

9. 使用 value, ok 模式避免零值混淆:

// 问题:无法区分零值和键不存在
value := m["key"]  // 如果 key 不存在,返回零值

// 解决:使用 value, ok 模式
if value, ok := m["key"]; ok {
    fmt.Println("键存在,值为:", value)
} else {
    fmt.Println("键不存在")
}

10. map 的零值:

// map 的零值是 nil
var m map[string]int
fmt.Println(m == nil)  // true
fmt.Println(len(m))   // 0

// 可以向 nil map 读取和写入
value := m["key"]  // 返回零值,不 panic
m["new"] = 1    // 自动初始化 map

Go 1.25 新特性

range over integers

Go 1.25 引入了整数范围的迭代,简化了循环写法。

package main

import "fmt"

func main() {
    // 传统方式
    for i := 0; i < 5; i++ {
        fmt.Println("传统:", i)
    }

    // Go 1.25 新方式:range over integers
    for i := range 5 {
        fmt.Println("新方式:", i)
    }

    // 带起始值
    for i := range 3 : 7 {
        fmt.Println("范围 3-6:", i)
    }
}

改进的 math/bits 包

package main

import (
    "fmt"
    "math/bits"
)

func main() {
    // 计算前导零
    x := uint(16)
    fmt.Println("前导零:", bits.LeadingZeros(x))

    // 计算尾随零
    fmt.Println("尾随零:", bits.TrailingZeros(x))

    // 计算设置位数
    fmt.Println("设置位数:", bits.OnesCount(x))
}

新的 slices 包

package main

import (
    "cmp"
    "fmt"
    "slices"
)

func main() {
    numbers := []int{5, 2, 8, 1, 9}

    // 排序
    slices.Sort(numbers)
    fmt.Println("排序后:", numbers)

    // 二分查找
    index, found := slices.BinarySearch(numbers, 8)
    fmt.Printf("查找 8: 索引=%d, 找到=%v\n", index, found)

    // 检查元素是否存在
    fmt.Println("包含 5:", slices.Contains(numbers, 5))

    // 比较切片
    numbers2 := []int{1, 2, 5, 8, 9}
    fmt.Println("切片相等:", slices.Equal(numbers, numbers2))

    // 查找最大最小值
    fmt.Println("最小值:", slices.Min(numbers))
    fmt.Println("最大值:", slices.Max(numbers))

    // 反转切片
    slices.Reverse(numbers)
    fmt.Println("反转后:", numbers)

    // 使用自定义比较函数排序
    names := []string{"Alice", "Bob", "Charlie"}
    slices.SortFunc(names, cmp.Compare)
    fmt.Println("排序后的名字:", names)
}

新的 maps 包

package main

import (
    "fmt"
    "maps"
)

func main() {
    m1 := map[string]int{
        "apple":  5,
        "banana": 3,
        "orange": 7,
    }

    m2 := map[string]int{
        "apple":  5,
        "banana": 3,
        "orange": 7,
    }

    // 比较 map 是否相等
    fmt.Println("map 相等:", maps.Equal(m1, m2))

    // 复制 map
    m3 := maps.Clone(m1)
    m3["grape"] = 10
    fmt.Println("原 map:", m1)
    fmt.Println("复制的 map:", m3)

    // 获取所有键
    keys := maps.Keys(m1)
    fmt.Println("所有键:", keys)

    // 获取所有值
    values := maps.Values(m1)
    fmt.Println("所有值:", values)
}