Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

unique 包详解

概述

unique 包提供了用于规范化(“驻留”)可比较值的功能。

主要用途:

  • 值驻留(interning)
  • 全局唯一标识符生成
  • 高效的值比较
  • 内存优化(通过共享重复值)
  • 并发安全的值去重

核心概念:

  • 驻留(Interning):确保相同值的多个副本共享同一存储
  • Handle:值的全局唯一标识符
  • 泛型支持:适用于任何可比较类型
  • 并发安全:可安全地在多个 goroutine 中使用

Go 版本要求:Go 1.23+

包导入

import "unique"

类型详解(按 A-Z 分层归类)

H

Handle

type Handle[T comparable] struct {
    // 包含导出或未导出的字段
}

作用:类型 T 的某个值的全局唯一标识符

说明:

  • 两个 Handle 比较相等,当且仅当用于创建这两个 Handle 的值也相等
  • Handle 的比较是微不足道的,通常比比较用于创建它们的值要高效得多
  • Handle 是不可变的,创建后不能修改
  • Handle 可以安全地在多个 goroutine 之间共享

示例:

// 创建 Handle
h1 := unique.Make("hello")
h2 := unique.Make("hello")
h3 := unique.Make("world")

// 比较 Handle
fmt.Println(h1 == h2)  // true (相同的值)
fmt.Println(h1 == h3)  // false (不同的值)

// 获取原始值
value := h1.Value()
fmt.Println(value)  // "hello"

// Handle 可以存储在 map 中
handleMap := make(map[unique.Handle[string]]int)
handleMap[h1] = 1
handleMap[h2] = 2  // 会覆盖 h1,因为 h1 == h2
fmt.Println(len(handleMap))  // 1

函数详解(按 A-Z 分层归类)

M

Make

func Make[T comparable](value T) Handle[T]

作用:为类型 T 的值返回一个全局唯一的 Handle

参数说明:

  • value:要创建 Handle 的值

返回值:

  • 值的 Handle

说明:

  • 当且仅当用于生成 Handle 的值相等时,Handle 才相等
  • Make 对于多个 goroutine 的并发使用是安全的
  • 底层实现使用并发安全的映射来存储规范化的值
  • 使用弱引用,允许垃圾回收器在未使用时回收值

示例:

// 字符串驻留
h1 := unique.Make("hello")
h2 := unique.Make("hello")
h3 := unique.Make("world")

fmt.Println(h1 == h2)  // true
fmt.Println(h1 == h3)  // false

// 整数驻留
n1 := unique.Make(42)
n2 := unique.Make(42)
fmt.Println(n1 == n2)  // true

// 结构体驻留
type Point struct {
    X, Y int
}
p1 := unique.Make(Point{1, 2})
p2 := unique.Make(Point{1, 2})
p3 := unique.Make(Point{3, 4})
fmt.Println(p1 == p2)  // true
fmt.Println(p1 == p3)  // false

// 切片不能用于 Make(不可比较)
// slice := []int{1, 2, 3}
// h := unique.Make(slice)  // 编译错误

Handle 方法详解(按 A-Z 分层归类)

V

Value

func (h Handle[T]) Value() T

作用:返回生成 Handle 的 T 值的浅拷贝

返回值:

  • 原始值的拷贝

说明:

  • Value 对于多个 goroutine 的并发使用是安全的
  • 返回的是浅拷贝,对于引用类型(如 map、slice、pointer)不会深拷贝
  • 每次调用都会返回一个新的拷贝

示例:

// 获取原始值
h := unique.Make("hello")
value := h.Value()
fmt.Println(value)  // "hello"

// 结构体示例
type Config struct {
    Name string
    Value int
}
h := unique.Make(Config{Name: "test", Value: 42})
config := h.Value()
fmt.Printf("%+v\n", config)  // {Name:test Value:42}

// 修改返回的值不会影响原始值
config.Value = 100
config2 := h.Value()
fmt.Printf("%+v\n", config2)  // {Name:test Value:42} (未受影响)

// 并发安全
go func() {
    _ = h.Value()
}()
_ = h.Value()

典型示例

1. 基本字符串驻留

package main

import (
    "fmt"
    "unique"
)

func main() {
    // 创建字符串的 Handle
    h1 := unique.Make("hello")
    h2 := unique.Make("hello")
    h3 := unique.Make("world")
    
    // 比较 Handle 比比较字符串更高效
    fmt.Println(h1 == h2)  // true
    fmt.Println(h1 == h3)  // false
    
    // 获取原始值
    fmt.Println(h1.Value())  // "hello"
}

2. 使用 Handle 作为 Map 键

package main

import (
    "fmt"
    "unique"
)

func main() {
    // 使用 Handle 作为 map 的键
    counts := make(map[unique.Handle[string]]int)
    
    texts := []string{"apple", "banana", "apple", "cherry", "banana", "apple"}
    
    for _, text := range texts {
        h := unique.Make(text)
        counts[h]++
    }
    
    // 统计结果
    for h, count := range counts {
        fmt.Printf("%s: %d\n", h.Value(), count)
    }
    // 输出:
    // apple: 3
    // banana: 2
    // cherry: 1
}

3. 结构体驻留

package main

import (
    "fmt"
    "unique"
)

type Point struct {
    X, Y int
}

func main() {
    // 创建结构体的 Handle
    p1 := unique.Make(Point{1, 2})
    p2 := unique.Make(Point{1, 2})
    p3 := unique.Make(Point{3, 4})
    
    // 比较结构体 Handle
    fmt.Println(p1 == p2)  // true
    fmt.Println(p1 == p3)  // false
    
    // 在 map 中使用
    pointSet := make(map[unique.Handle[Point]]bool)
    pointSet[p1] = true
    pointSet[p2] = true  // 会覆盖 p1
    pointSet[p3] = true
    
    fmt.Println(len(pointSet))  // 2
}

4. 并发安全的驻留

package main

import (
    "fmt"
    "sync"
    "unique"
)

func main() {
    var wg sync.WaitGroup
    handles := make([]unique.Handle[string], 1000)
    
    // 多个 goroutine 并发创建 Handle
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(start int) {
            defer wg.Done()
            for j := 0; j < 100; j++ {
                handles[start+j] = unique.Make("hello")
            }
        }(i * 100)
    }
    
    wg.Wait()
    
    // 所有 Handle 都应该相等
    allEqual := true
    for i := 1; i < len(handles); i++ {
        if handles[i] != handles[0] {
            allEqual = false
            break
        }
    }
    
    fmt.Println("All handles equal:", allEqual)  // true
}

5. 优化大量重复字符串

package main

import (
    "fmt"
    "unique"
)

type LogEntry struct {
    Level   unique.Handle[string]
    Message unique.Handle[string]
    Count   int
}

func main() {
    entries := []LogEntry{}
    
    // 模拟日志处理
    levels := []string{"INFO", "WARN", "ERROR", "INFO", "WARN", "INFO"}
    messages := []string{"Started", "Processing", "Failed", "Started", "Processing", "Started"}
    
    for i := range levels {
        entry := LogEntry{
            Level:   unique.Make(levels[i]),
            Message: unique.Make(messages[i]),
            Count:   i + 1,
        }
        entries = append(entries, entry)
    }
    
    // 打印日志
    for _, entry := range entries {
        fmt.Printf("[%s] %s (count: %d)\n", 
            entry.Level.Value(), entry.Message.Value(), entry.Count)
    }
    
    // 内存优化:所有 "INFO" 共享同一个字符串存储
    fmt.Printf("\nFirst INFO handle: %p\n", &entries[0].Level)
    fmt.Printf("Second INFO handle: %p\n", &entries[3].Level)
    fmt.Println("Handles equal:", entries[0].Level == entries[3].Level)
}

6. 使用 Handle 进行快速比较

package main

import (
    "fmt"
    "unique"
)

type Document struct {
    Tags []unique.Handle[string]
}

func main() {
    // 创建文档标签
    doc1 := Document{
        Tags: []unique.Handle[string]{
            unique.Make("go"),
            unique.Make("programming"),
            unique.Make("backend"),
        },
    }
    
    doc2 := Document{
        Tags: []unique.Handle[string]{
            unique.Make("go"),
            unique.Make("programming"),
            unique.Make("backend"),
        },
    }
    
    // 快速比较标签
    if len(doc1.Tags) == len(doc2.Tags) {
        allMatch := true
        for i := range doc1.Tags {
            if doc1.Tags[i] != doc2.Tags[i] {
                allMatch = false
                break
            }
        }
        fmt.Println("Documents have same tags:", allMatch)
    }
    
    // 比使用字符串比较高效得多
}

7. 实现对象池

package main

import (
    "fmt"
    "unique"
)

type Connection struct {
    Host unique.Handle[string]
    Port int
}

func main() {
    // 模拟连接池
    connections := make(map[unique.Handle[string]]*Connection)
    
    hosts := []string{"server1.example.com", "server2.example.com", 
                      "server1.example.com", "server3.example.com"}
    
    for i, host := range hosts {
        h := unique.Make(host)
        
        if conn, ok := connections[h]; ok {
            fmt.Printf("Reusing connection to %s (port %d)\n", 
                conn.Host.Value(), conn.Port)
        } else {
            conn := &Connection{
                Host: h,
                Port: 8080 + i,
            }
            connections[h] = conn
            fmt.Printf("Created connection to %s (port %d)\n", 
                conn.Host.Value(), conn.Port)
        }
    }
    
    fmt.Printf("\nTotal connections: %d\n", len(connections))
    // 输出:3 (而不是 4,因为 server1 重复)
}

8. 处理配置项

package main

import (
    "fmt"
    "unique"
)

type ConfigKey struct {
    Section unique.Handle[string]
    Name    unique.Handle[string]
}

type ConfigValue struct {
    Key   ConfigKey
    Value string
}

func main() {
    config := make(map[ConfigKey]ConfigValue)
    
    // 添加配置项
    sections := []string{"database", "cache", "database", "logging"}
    names := []string{"host", "ttl", "port", "level"}
    values := []string{"localhost", "3600", "5432", "info"}
    
    for i := range sections {
        key := ConfigKey{
            Section: unique.Make(sections[i]),
            Name:    unique.Make(names[i]),
        }
        
        config[key] = ConfigValue{
            Key:   key,
            Value: values[i],
        }
    }
    
    // 查找配置
    dbHostKey := ConfigKey{
        Section: unique.Make("database"),
        Name:    unique.Make("host"),
    }
    
    if val, ok := config[dbHostKey]; ok {
        fmt.Printf("database.host = %s\n", val.Value)
    }
    
    fmt.Printf("Total config entries: %d\n", len(config))
}

9. 去重大量数据

package main

import (
    "fmt"
    "unique"
)

func deduplicate(items []string) []unique.Handle[string] {
    seen := make(map[unique.Handle[string]]bool)
    result := make([]unique.Handle[string], 0)
    
    for _, item := range items {
        h := unique.Make(item)
        if !seen[h] {
            seen[h] = true
            result = append(result, h)
        }
    }
    
    return result
}

func main() {
    items := []string{
        "apple", "banana", "apple", "orange",
        "banana", "apple", "grape", "orange",
    }
    
    uniqueItems := deduplicate(items)
    
    fmt.Printf("Original: %d items\n", len(items))
    fmt.Printf("Unique: %d items\n", len(uniqueItems))
    
    fmt.Println("Unique items:")
    for _, h := range uniqueItems {
        fmt.Println(" -", h.Value())
    }
}

10. 缓存系统中的键

package main

import (
    "fmt"
    "sync"
    "unique"
)

type CacheKey struct {
    Namespace unique.Handle[string]
    Key       unique.Handle[string]
}

type Cache struct {
    mu   sync.RWMutex
    data map[CacheKey]any
}

func NewCache() *Cache {
    return &Cache{
        data: make(map[CacheKey]any),
    }
}

func (c *Cache) Get(ns, key string) (any, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    
    cacheKey := CacheKey{
        Namespace: unique.Make(ns),
        Key:       unique.Make(key),
    }
    
    value, ok := c.data[cacheKey]
    return value, ok
}

func (c *Cache) Set(ns, key string, value any) {
    c.mu.Lock()
    defer c.mu.Unlock()
    
    cacheKey := CacheKey{
        Namespace: unique.Make(ns),
        Key:       unique.Make(key),
    }
    
    c.data[cacheKey] = value
}

func main() {
    cache := NewCache()
    
    // 设置缓存
    cache.Set("user", "123", map[string]string{"name": "Alice"})
    cache.Set("user", "456", map[string]string{"name": "Bob"})
    cache.Set("product", "789", map[string]string{"name": "Widget"})
    
    // 获取缓存
    if value, ok := cache.Get("user", "123"); ok {
        fmt.Printf("User 123: %+v\n", value)
    }
    
    // 重复的键会使用相同的 Handle
    cache.Set("user", "123", map[string]string{"name": "Alice Updated"})
    
    fmt.Println("Cache operations completed")
}

最佳实践

1. 用于频繁比较的场景

// 推荐:在需要频繁比较相同值时使用
type Request struct {
    Method unique.Handle[string]
    Path   unique.Handle[string]
}

// Handle 比较比字符串比较快得多
if req1.Method == req2.Method && req1.Path == req2.Path {
    // 快速比较
}

2. 作为 Map 键使用

// 推荐:使用 Handle 作为 map 键
counts := make(map[unique.Handle[string]]int)

for _, item := range items {
    h := unique.Make(item)
    counts[h]++
}

3. 并发安全的驻留

// unique.Make 是并发安全的
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        _ = unique.Make("shared value")
    }()
}
wg.Wait()

4. 避免用于大型结构

// 不推荐:大型结构体会占用大量内存
type LargeStruct struct {
    Data [10000]byte
}
h := unique.Make(LargeStruct{})  // 可能不划算

// 推荐:仅对小型、频繁重复的值使用
h := unique.Make("status")

5. 理解弱引用行为

// Handle 使用弱引用
// 如果没有 Handle 引用某个值,它可能被垃圾回收
func example() {
    h := unique.Make("temp")
    // h 离开作用域后,"temp" 可能被回收
}

与其他包配合

sync 包

import (
    "sync"
    "unique"
)

// 并发安全的缓存
type Cache struct {
    mu   sync.RWMutex
    data map[unique.Handle[string]]any
}

容器/集合包

import (
    "unique"
)

// 使用 Handle 的集合
type StringSet struct {
    items map[unique.Handle[string]]struct{}
}

func (s *StringSet) Add(item string) {
    if s.items == nil {
        s.items = make(map[unique.Handle[string]]struct{})
    }
    s.items[unique.Make(item)] = struct{}{}
}

注意事项

1. 仅适用于可比较类型

// 编译错误:slice 不可比较
// slice := []int{1, 2, 3}
// h := unique.Make(slice)

// 正确:使用数组或 struct
array := [3]int{1, 2, 3}
h := unique.Make(array)

2. Handle 比较 vs 值比较

// Handle 相等意味着值相等
h1 := unique.Make("hello")
h2 := unique.Make("hello")
fmt.Println(h1 == h2)  // true

// 但获取的值是不同的拷贝
v1 := h1.Value()
v2 := h2.Value()
fmt.Println(&v1 == &v2)  // false

3. 内存考虑

// unique.Make 会存储值的副本
// 对于大量唯一值,可能增加内存使用

// 推荐:用于重复值多的场景
for _, repeated := range manyRepeatedValues {
    h := unique.Make(repeated)  // 节省内存
}

// 不推荐:用于几乎都不同的值
for _, unique := range allUniqueValues {
    h := unique.Make(unique)  // 可能浪费内存
}

4. 弱引用行为

// Handle 使用弱引用
// 没有 Handle 引用时,值可能被回收

func createHandle() unique.Handle[string] {
    return unique.Make("temp")
}

h := createHandle()
// h 仍然有效,可以正常使用
fmt.Println(h.Value())

5. 浅拷贝语义

// Value() 返回浅拷贝
type Data struct {
    Slice []int
}

h := unique.Make(Data{Slice: []int{1, 2, 3}})
d := h.Value()
d.Slice[0] = 100  // 会影响底层数据

// 对于引用类型,需要注意共享状态

6. 性能特性

// Handle 比较是 O(1)
// 值比较可能是 O(n)

// 推荐:频繁比较时使用 Handle
if handle1 == handle2 {
    // 快速指针比较
}

// 不推荐:每次都获取值比较
if handle1.Value() == handle2.Value() {
    // 可能较慢
}

快速参考

类型速查表

类型说明
Handle[T]类型 T 的值的全局唯一标识符

函数速查表

函数说明
Make[T](value)为值创建全局唯一的 Handle

Handle 方法速查表

方法说明
Value()返回生成 Handle 的原始值的拷贝
==比较两个 Handle 是否相等

适用类型

类型是否可用说明
基本类型✅int, string, bool 等
指针✅*T
数组✅[N]T
结构体✅所有字段都可比较
接口✅动态类型可比较
切片❌不可比较
Map❌不可比较
函数❌不可比较

常见模式

// 基本使用
h := unique.Make(value)
if h1 == h2 {
    // Handle 相等
}
value := h.Value()

// 作为 map 键
m := make(map[unique.Handle[string]]int)
m[unique.Make("key")] = value

// 去重
seen := make(map[unique.Handle[string]]bool)
for _, item := range items {
    h := unique.Make(item)
    if !seen[h] {
        seen[h] = true
        // 处理唯一项
    }
}

// 并发安全
var wg sync.WaitGroup
for i := 0; i < N; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        _ = unique.Make("shared")
    }()
}
wg.Wait()

总结

unique 包提供了强大的值驻留功能:

核心功能:

  • 全局唯一标识符(Handle)生成
  • 高效的值比较
  • 并发安全的值驻留
  • 泛型支持任意可比较类型

主要类型:

  • Handle[T]:值的全局唯一标识符

主要函数:

  • Make[T](value):创建 Handle

使用场景:

  1. 频繁比较相同值的场景
  2. 作为 map 的键
  3. 字符串驻留
  4. 对象池实现
  5. 缓存系统键
  6. 数据去重

使用建议:

  1. 仅用于可比较类型
  2. 适合重复值多的场景
  3. 利用 Handle 的快速比较
  4. 理解弱引用行为
  5. 注意浅拷贝语义
  6. 并发安全,无需额外加锁

典型用法:

// 创建 Handle
h := unique.Make("hello")

// 快速比较
if h1 == h2 {
    // Handle 比较
}

// 作为 map 键
m := make(map[unique.Handle[string]]int)
m[h] = value

// 获取值
value := h.Value()

通过 unique 包,可以高效地实现值驻留,优化内存使用和比较性能,特别适用于处理大量重复值的场景。