通过“王小一”的名字,首先get到W大写首字母的位置(这一步就是哈希函数映射的作用!),然后翻看字典根据W栏,找到其里面的“王小一”。当同时存在“王小二”的时候,同样首先根据哈希函数,找到W大写首字母的位置,然后发现W栏的下面,已经填上了“王小一”(这就是哈希冲突!),此时怎么办?就继续在王小二的后面,以链表连接的形式,继续连接上“王小二”。
- 王小一----hash映射----W的位置
- W的位置----王小一----王小二(遇到hash冲突,链式法则让王小二继续链接在王小一的后面)
上述的例子,可以很好地理解hash函数的作用,可以很好地理解hash冲突,但没看到键-值对(key-value,即entry),理论上应该是unordered_set的原理,接着看下面的unordered_map原理的例子。
- 首先,拿key:101011用hash函数找到数组里,该entry的位置,即idx:1,即key:101011----hash函数----idx:1。
- 然后,在idx:1这找其指向的是什么entry,显然指的entry是key:101011 val:张三。
- 此时,如果拿李四的学号来找,即key:102011----hash函数----idx:1,用hash函数映射也是1,那么就发生hash冲突,此时将李四link在张三后面。
-
hash函数设计越好,冲突就越少发生。
-
hash的结构
数组+链表,或数组+二叉树(拉链法)
数组(开放寻址法、 open address)。
-
hash冲突的解决方式
对于开放寻址法,最典型为线性探测(linear probing),即当前位置如果冲突,直接顺延到下一位置。
对于拉链法,则往后面链接node即可。
-
解决hash冲突,除了拉链法,还有开放寻址法,即如果idx:1上冲突了,就继续放在后面即2上,如果2也已经填了,就继续往后排放,空间不够就扩容,Java的ThreadLocal就是开放寻址法。
-
Java的HashMap是拉链法,对于HashMap,当某个idx位置的链表长度大于8,链表会转换成树,小于6时,又会转换回为链表,不是7的原因是,避免反复横跳,转换过程也有开销。
-
hash函数的设计方法:定址法,数字分析法,折叠法,随机数法和除留余数法等等。