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
使用场景:
- 频繁比较相同值的场景
- 作为 map 的键
- 字符串驻留
- 对象池实现
- 缓存系统键
- 数据去重
使用建议:
- 仅用于可比较类型
- 适合重复值多的场景
- 利用 Handle 的快速比较
- 理解弱引用行为
- 注意浅拷贝语义
- 并发安全,无需额外加锁
典型用法:
// 创建 Handle
h := unique.Make("hello")
// 快速比较
if h1 == h2 {
// Handle 比较
}
// 作为 map 键
m := make(map[unique.Handle[string]]int)
m[h] = value
// 获取值
value := h.Value()
通过 unique 包,可以高效地实现值驻留,优化内存使用和比较性能,特别适用于处理大量重复值的场景。