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 包,可以高效地实现值驻留,优化内存使用和比较性能,特别适用于处理大量重复值的场景。