在实际应用中我们需要用到一种动态集合结构。散列表是普通数组的推广,可以对数组直接寻址,故可以在O(1)
时间内访问数组的任意元素。散列表中的思想是把关键字映射到数组下标,是通过计算出来的,散列计算过程中容器出现碰撞,解决碰撞也是散列的一个难点,根本问题是哈希计算的问题。
直接访问表
当需要存储一个(1.....m-1)
的元素时,并且m
比较小,此时就可以用一个数组T
来存储所有从1-m
之间的数组。
比如需要存储:{4,6,8,3}
这几个元素,这里面最大的元素为8
,所以创建一个数组有8
个元素,把对应的元素放到数组槽里面即可。
直接寻址有一个问题就是当m
很大是,需要的数组T
就会很大,即使需要存储的元素比较少,比如:{4,1,6,2^64}
,这里只有四个元素,最大值为2^64
,如果创建一个T
的数组则需要很大的存储空间。
散列表原理和实现
全域散列
开放寻址法
go map实现
golang中map是一个kv对集合。底层使用hash table,用链表来解决冲突,通过编译器配合runtime,所有的map对象都是共用一份代码。
hash 函数
hash
函数通过cpu
的架构来选择不同的计算方式,有aes
和memhash
两种实现方式,memhash
是借鉴xxhash
和cityhash
原理进行开发,其速度超级快,且采用的是随机哈希的方式,在每一运行过程中都有一个种子元素,同一个key
哈希出来的值都是不一样的,这样就避免了哈希碰撞攻击,
源码在src/runtime/hase64.go
文件中,
解决碰撞
map
中主要通过bmap
这个结构来存储k/v
的,bmap
在hmap
中是一个动态数组,每个bmap
对象由8个k/v
组成,如果一个bmap
填充满了,bmap
中有一个指向下一个bmap
的指针,这样就通过链表解决了冲突的情况。