Files
bl/common/utils/emap.go
昔念 ae88790ed3 修改range,支持技能效果
Updated comments for clarity and consistency in the OrderedMap implementation, enhancing the understanding of the data structure and its methods.
2025-10-22 00:57:25 +08:00

222 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package utils
import (
"fmt"
"hash/maphash"
"sync"
)
// MapEntry 表示有序Map中的单个键值对条目维护双向链表和哈希冲突链表指针
type MapEntry[K comparable, V any] struct {
key K
value V
// 双向链表指针:维护键值对的插入顺序
prev, next *MapEntry[K, V]
// 哈希冲突链表指针:处理哈希表中相同哈希值的键
hashNext *MapEntry[K, V]
// deleted 标记条目是否被逻辑删除(避免直接删除链表节点导致遍历断裂)
deleted bool
}
// OrderedMap 泛型有序Map实现支持按插入顺序遍历、并发安全且修复Range期间添加阻塞问题
type OrderedMap[K comparable, V any] struct {
mu sync.RWMutex // 读写锁:保证并发安全,读多写少场景更高效
hash *maphash.Hash // 哈希计算器:为键生成哈希值用于快速查找
hashTable map[uint64]*MapEntry[K, V] // 哈希表:存储哈希值到对应冲突链表头的映射
first *MapEntry[K, V] // 双向链表头:指向第一个插入的有效条目
last *MapEntry[K, V] // 双向链表尾:指向最后一个插入的有效条目
size int // 有效条目数量:排除已逻辑删除的条目
}
// NewOrderedMap 创建一个新的泛型有序Map实例
func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] {
return &OrderedMap[K, V]{
hash: &maphash.Hash{},
hashTable: make(map[uint64]*MapEntry[K, V]),
}
}
// hashKey 为指定键生成哈希值(内部方法,需在外层加锁保证并发安全)
// 注复杂类型需自定义序列化逻辑此处简化为通过fmt.Sprintf转为字符串后计算哈希
func (m *OrderedMap[K, V]) hashKey(key K) uint64 {
m.hash.Reset()
m.hash.WriteString(fmt.Sprintf("%v", key))
return m.hash.Sum64()
}
// lookup 查找指定键对应的条目、其哈希值及哈希冲突链表中的前驱条目(内部方法,需外层加锁)
// 返回值哈希值、目标条目未找到或已删除则为nil、哈希冲突链表中的前驱条目
func (m *OrderedMap[K, V]) lookup(key K) (hash uint64, entry, prevHashEntry *MapEntry[K, V]) {
hash = m.hashKey(key)
// 遍历哈希冲突链表,查找未删除的目标键
for entry = m.hashTable[hash]; entry != nil; prevHashEntry, entry = entry, entry.hashNext {
if !entry.deleted && entry.key == key {
break
}
}
return
}
// Store 存储键值对:键存在则更新值,键不存在则新增(按插入顺序追加到链表末尾)
func (m *OrderedMap[K, V]) Store(key K, value V) {
m.mu.Lock()
defer m.mu.Unlock()
hash, entry, prevHashEntry := m.lookup(key)
// 情况1键已存在且未删除直接更新值
if entry != nil {
entry.value = value
// 若条目曾被逻辑删除,恢复为有效状态并更新计数
if entry.deleted {
entry.deleted = false
m.size++
}
return
}
// 情况2键不存在创建新条目
entry = &MapEntry[K, V]{
key: key,
value: value,
}
// 2.1 将新条目添加到哈希冲突链表(处理哈希碰撞)
if prevHashEntry == nil {
// 哈希桶为空,直接作为桶的头节点
m.hashTable[hash] = entry
} else {
// 哈希桶已有节点,追加到冲突链表末尾
prevHashEntry.hashNext = entry
}
// 2.2 将新条目添加到双向链表末尾(保证插入顺序)
if m.last != nil {
// 链表已有节点,更新尾节点指针
entry.prev = m.last
m.last.next = entry
} else {
// 链表为空,新条目同时作为头节点
m.first = entry
}
// 更新尾节点为新条目
m.last = entry
// 增加有效条目计数
m.size++
}
// Load 查找指定键的值存在则返回值和true不存在则返回对应类型零值和false
func (m *OrderedMap[K, V]) Load(key K) (value V, exists bool) {
m.mu.RLock()
defer m.mu.RUnlock()
_, entry, _ := m.lookup(key)
// 仅当条目存在且未删除时,返回有效数据
if entry != nil && !entry.deleted {
return entry.value, true
}
return value, false
}
// LoadOrStore 查找并返回键对应的值存在则返回原值和true不存在则存储并返回新值和false
func (m *OrderedMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
m.mu.Lock()
defer m.mu.Unlock()
_, entry, _ := m.lookup(key)
// 情况1键已存在未删除返回原值
if entry != nil && !entry.deleted {
return entry.value, true
}
// 情况2键不存在执行存储逻辑
hash := m.hashKey(key)
entry = &MapEntry[K, V]{
key: key,
value: value,
}
// 2.1 处理哈希冲突,添加到对应哈希桶
if m.hashTable[hash] == nil {
m.hashTable[hash] = entry
} else {
// 遍历冲突链表到末尾,追加新条目
lastHashEntry := m.hashTable[hash]
for lastHashEntry.hashNext != nil {
lastHashEntry = lastHashEntry.hashNext
}
lastHashEntry.hashNext = entry
}
// 2.2 添加到双向链表末尾,保证插入顺序
if m.last != nil {
entry.prev = m.last
m.last.next = entry
} else {
m.first = entry
}
m.last = entry
m.size++
// 返回存储的新值和“未加载(新存储)”标记
return value, false
}
// Delete 逻辑删除指定键仅标记deleted为true不直接删除节点避免遍历链表断裂
func (m *OrderedMap[K, V]) Delete(key K) {
m.mu.Lock()
defer m.mu.Unlock()
_, entry, _ := m.lookup(key)
// 仅当条目存在且未删除时,执行逻辑删除并更新计数
if entry != nil && !entry.deleted {
entry.deleted = true
m.size--
}
}
// Range 按插入顺序遍历所有有效键值对:修复后支持遍历期间添加,新添加条目不影响当前遍历
// 回调函数f返回false时停止遍历
func (m *OrderedMap[K, V]) Range(f func(key K, value V) bool) {
// 第一步:加读锁复制快照(仅持有极短时间,避免阻塞写操作)
m.mu.RLock()
// 预分配切片容量(等于有效条目数),减少动态扩容开销
snapshot := make([]*MapEntry[K, V], 0, m.size)
current := m.first
// 遍历双向链表,收集所有未删除的有效条目
for current != nil {
if !current.deleted {
snapshot = append(snapshot, current)
}
current = current.next
}
// 关键复制完成后立即释放读锁允许Store等写操作执行
m.mu.RUnlock()
// 第二步:遍历快照切片,执行用户回调(无锁状态,安全且高效)
for _, entry := range snapshot {
if !f(entry.key, entry.value) {
return // 回调返回false终止遍历
}
}
}
// Len 返回当前Map中的有效条目数量排除已逻辑删除的条目
func (m *OrderedMap[K, V]) Len() int {
m.mu.RLock()
defer m.mu.RUnlock()
return m.size
}
// Clear 清空Map所有内容重置哈希表、双向链表和计数物理清空而非逻辑删除
func (m *OrderedMap[K, V]) Clear() {
m.mu.Lock()
defer m.mu.Unlock()
m.first = nil
m.last = nil
m.hashTable = make(map[uint64]*MapEntry[K, V])
m.size = 0
}