解决Hash冲突的一个主流思想是包容,可以用链表将冲突的元素都挂到一起。
type node = linkedlist.Node[string]
type hashSet struct {
bucket []*node //表空间
size int
nextLine int //标记待处理的旧表行
oldBucket []*node //旧表(仅在rehash过程中有内容)
}
当然也不能一味地放任下去,否则查询效率就无从谈起了。于是,我们需要限制容积率,并适时扩展表基。扩展需要进行重Hash,这里将重Hash过程分散到此后的每次增删查操作中。
func (s *hashSet) Insert(key string) bool {
code := hash(key)
index := code % uint32(len(s.bucket))
conflict := search(s.bucket[index], key)
if s.isMoving() { //重Hash过程中
if !conflict { //尝试从旧表中查找
index := code % uint32(len(s.oldBucket))
conflict = search(s.oldBucket[index], key)
}
s.moveLine() //推进重Hash过程
}
if !conflict {
unit := new(node)
unit.Val = key
unit.Next, s.bucket[index] = s.bucket[index], unit
s.size++
if !s.isMoving() && s.isCrowded() { //检查容积率是否超标
idx := array.SearchSuccessor(primes, uint32(len(s.bucket)))
if idx < len(primes) {
s.resize(primes[idx]) //扩容,即将所有元素重Hash到更大的表中
} }
return true
}
return false
}
对应地,删除元素时考虑缩小表基。设置适当的容积率上下界,可以避免在重Hash过程中出现新的重Hash请求。
func (s *hashSet) Remove(key string) bool {
code, done := hash(key), false
index := code % uint32(len(s.bucket))
s.bucket[index], done = remove(s.bucket[index], key)
if s.isMoving() {
if !done { //尝试从旧表中删除
index = code % uint32(len(s.oldBucket))
s.oldBucket[index], done = remove(s.oldBucket[index], key)
}
s.moveLine() //推进重Hash过程
}
if done {
s.size--
if !s.isMoving() && s.isWasteful() {
idx := array.SearchFirstGE(primes, uint32(len(s.bucket))) - 1
if idx >= 0 {
s.resize(primes[idx]) //减容
} } }
return done
}