golang系列之-sync.Map(HashTrieMap)
从1.24版开始,sync.Map改用HashTrieMap重构,与之前的双map实现不同,HashTrieMap更像是一个B树,简单的示例图如下
1 | // root -> | idx0 | idx1 | ... | idx15 | |
使用哈希函数生成64位的哈希值,从高到低4位为一个idx,最多有16层,每个节点可容纳16个元素,最多可容纳16^16=2^64个元素
当前go版本:1.24
HashTrieMap的开关放在文件
src/internal/buildcfg/exp.go
的函数ParseGOEXPERIMENT
中
数据结构
1 | type HashTrieMap[K comparable, V any] struct { |
核心方法
Load
根据key获取value,具体逻辑如下
- 从根节点开始,使用hash(key)计算出idx用于检索children
- 如果子节点是nil,即没找到,返回
- 如果子节点是叶子节点,搜索链表并返回
1 | func (ht *HashTrieMap[K, V]) Load(key K) (value V, ok bool) { |
Store
存储key/value。本质就是Swap方法,但丢弃其返回值,具体看Swap
1 | func (ht *HashTrieMap[K, V]) Store(key K, old V) { |
LoadOrStore
根据key获取value,获取失败则保存key/value,具体逻辑如下
- Load
- 从根节点开始,使用hash(key)计算出idx用于检索children
- 如果子节点是叶子节点,搜索链表,有比对成功的key/value则返回
- Store
- 纪录搜索中断位置的curr节点指针i和子节点指针slot、n
- 如果n为nil,直接写入children
- 如果n为叶子节点,放进链表,或者分裂后再存储进children
1 | func (ht *HashTrieMap[K, V]) LoadOrStore(key K, value V) (result V, loaded bool) { |
Delete
删除指定key,具体看LoadAndDelete
1 | func (ht *HashTrieMap[K, V]) Delete(key K) { |
LoadAndDelete
根据key获取value并删除该key,具体逻辑如下
- Load
- 从根节点开始,使用hash(key)计算出idx用于检索children
- 如果子节点是nil,返回
- 如果子节点是叶子节点,搜索链表,有比对成功的key/value则纪录起来
- Delete
- 纪录子节点位置的curr节点指针i和子节点指针slot、n
- 如果删除key后,叶子节点链表不为空,回写并返回
- 如果删除key后,叶子节点链表为空,从curr开始往上搜索删除空的节点
1 | func (ht *HashTrieMap[K, V]) LoadAndDelete(key K) (value V, loaded bool) { |
CompareAndDelete
根据key和value搜索,如找到则删除该数据,具体逻辑如下
- 从根节点开始,使用hash(key)计算出idx用于检索children
- 如果子节点是nil,返回
- 如果子节点是叶子节点,搜索链表,有比对成功的key/value则删除
- 如果删除key后,叶子节点链表不为空,回写并返回
- 如果删除key后,叶子节点链表为空,从curr开始往上搜索删除空的节点
1 | func (ht *HashTrieMap[K, V]) CompareAndDelete(key K, old V) (deleted bool) { |
Swap
用新的value替换旧的value,具体逻辑如下
- 查找可替换key的位置
- 从根节点开始,使用hash(key)计算出idx用于检索children
- 如果子节点为nil或叶子节点,且当前节点未被删除,纪录位置信息,结束查找
- 替换value
- 如果子节点是叶子节点,在链表查找并替换value,替换成功回写并返回
- 如果子节点是nil
- 如果slot为空,直接写入
- 如果slot不为空,放进链表,或者分裂后再存储进children
1 | func (ht *HashTrieMap[K, V]) Swap(key K, new V) (previous V, loaded bool) { |
CompareAndSwap
根据key和value搜索,如找到则用新的value替换旧的value,具体逻辑如下
- 从根节点开始,使用hash(key)计算出idx用于检索children
- 如果子节点是nil,返回
- 如果子节点是叶子节点,搜索链表,有比对成功的key/value则替换
- 替换失败,直接返回;替换成功,回写并返回
1 | func (ht *HashTrieMap[K, V]) CompareAndSwap(key K, old, new V) (swapped bool) { |
Range
遍历整个HashTrieMap,逻辑如下
- 从根节点开始遍历children
- 子节点为nil,跳过
- 子节点不是叶子节点,递归遍历children
- 子节点是叶子节点,遍历整个链表
- key/value传递给用户自定义函数,失败中断整个遍历
1 | func (ht *HashTrieMap[K, V]) Range(yield func(K, V) bool) { |
All
封装Range方法并返回
1 | func (ht *HashTrieMap[K, V]) All() func(yield func(K, V) bool) { |
Clear
清空整个HashTrieMap,直接用新的nil节点更新root
1 | func (ht *HashTrieMap[K, V]) Clear() { |