哈希表是一种通过键值对存储数据的数据结构,它可以快速地查找、插入和删除元素。
例如,你可以创建一个哈希表来存储学生的姓名和成绩:
var scores = map[string]int {"Alice": 90, "Bob": 80, "Charlie": 85}
然后你可以通过姓名来查找或修改成绩:
fmt.Println(scores["Alice"]) // 90
scores["Bob"] = 95 // update Bob's score
或者你可以遍历哈希表中的所有键值对:
for name, score := range scores { fmt.Println(name, score) }
Go 语言中的哈希表是通过 map 类型来表示的,它在声明时需要指定键和值的类型。
例如,你可以创建一个字符串到整型的哈希表:
var m = map[string]int {}
或者你可以创建一个整型到字符串切片的哈希表:
var n = map[int][]string {}
Go 语言中的 map 是一个结构体,它包含了一个指向哈希桶数组的指针、哈希桶数组的长度、哈希函数种子和元素计数器。
哈希桶是一个存储键值对的数组,它有八个槽位,每个槽位可以存储一个键值对或者一个溢出指针。
溢出指针是一个指向另一个哈希桶的指针,它用于解决哈希冲突,即不同的键映射到同一个哈希桶的情况。
哈希函数是一个将任意长度的输入转换为固定长度的输出的函数,它用于计算键在哈希桶数组中的索引。
例如,假设我们有一个简单的哈希函数,它将一个整数除以 16 并取余数作为输出:
func hash(x int) int { return x % 16 }
那么这个哈希函数会将不同的整数映射到 0 到 15 的范围内:
hash(1) // 1
hash(9) // 9
hash(17) // 1
hash(25) // 9
Go 语言中使用了一种名为 aeshash 的哈希函数,它基于 AES 加密算法,并且使用随机生成的种子来增加安全性和随机性。
Go 语言中还提供了 sync.Map 类型来支持并发访问和修改 map ,它内部使用了两个 map 和一把互斥锁来实现线程安全。
// 导入 sync 包
import "sync"
// 定义一个 sync.Map 类型的变量
var visits sync.Map
// 定义一个函数来增加某个网页的访问量
func addVisit(page string) {
// 使用 LoadOrStore 方法来获取或设置键值对
// 如果键存在,则返回键对应的值和 true
// 如果键不存在,则设置键值对为 page 和 1,并返回 0 和 false
value, ok := visits.LoadOrStore(page, 1)
if ok {
// 如果键存在,则将值加一,并使用 Store 方法更新键值对
visits.Store(page, value.(int)+1)
}
}
// 定义一个函数来获取某个网页的访问量
func getVisit(page string) int {
// 使用 Load 方法来获取键值对
// 如果键存在,则返回键对应的值和 true
// 如果键不存在,则返回 nil 和 false
value, ok := visits.Load(page)
if ok {
// 如果键存在,则将值转换为整数类型并返回
return value.(int)
}
return 0
}
哈希表在 Go 语言中的扩容机制和优化策略,如增量扩容、随机探测法等
当哈希表中元素数量超过容量乘以负载因子(默认为6.5)时,就会触发扩容操作。扩容操作会创建一个新的 hbucket 数组,并将旧数组中的元素重新散列到新数组中。为了避免一次性拷贝所有元素造成性能下降,Go语言采用了增量扩容(incremental hash table growth)1 的策略,在每次插入或删除元素时都会拷贝一小部分旧元素到新数组中。
另外,Go语言也使用了随机探测法(random probing)1 来解决散列冲突。当两个不同的键被散列到同一个位置时,而该位置已经被占用时,就会发生散列冲突。随机探测法会从该位置开始向后寻找下一个空闲位置,并将新元素插入