hash 哈希,有的也翻译为散列,哈希表通常基于数组实现,元素存取效率高。
java中常见的hash集合都是使用哈希表来存储元素
- map:HashMap、LinkedHashMap、Hashtable
- set:HashSet、LinkedHashSet
哈希表中的常见概念
桶 bucket:哈希表中存储元素的位置叫做桶,数组中的一个位置即一个 bucket。
哈希表的容量 capacity:即桶的个数、数组长度。
哈希冲突:又叫做哈希碰撞,多个元素计算得到的哈希地址相同。哈希冲突是否会导致一个bucket中存储多个元素,这要看哈希冲突的解决方式,比如链表法则会在一个bucket中以链表形式存储多个哈希地址相同的元素,开放地址法则不会,会重新寻找新的空桶(哈希地址)。
重哈希 rehashing:也叫作再散列,哈希表存储的元素数量超过阈值时,哈希表自动扩容,重新分配存储位置,将原有元素都复制到新哈希表中。重哈希对性能影响大,应该避免频繁重哈希。
哈希函数常见的构建方式(哈希算法)
哈希函数(hash function)的构建原则
- 便于计算,计算不能太复杂,以减少计算的时间开销。
- 计算得到的地址分布均匀,即对任一关键字key,f(key) 对应不同地址的概率相等,以尽可能减少冲突。
说明:参与计算的关键字key不是对象本身,而是对象的哈希值 obj.hashCode(),哈希函数 hash(key) 是对哈希值(整数)做计算、处理。
1、除留余数法
hash(key) = key % p
p<= 哈希表容量n。优点是可以使计算得到的哈希地址比较均匀、分散,HashMap使用的即除留余数法的变种。
2、平方取中法
先求出关键字key的平方值,然后按需取平方值的中间几位作为哈希地址。比如哈希表长度1000,数组下标都是三位数000~999,则取平方值的中间3位。取中的原因:平方后中间几位和关键字中每一位都相关,不同关键字会以较高的概率产生不同的哈希地址。
3、分段叠加法
按哈希表地址位数将关键字key分成位数相等的几部分,最后一部分较短的可以舍弃,然后将这些分段对齐相加,舍弃多余的高进位,即得到对应的哈希地址。叠加时可以直接叠加,也可以做一些特殊操作,比如奇数段正序、偶数段倒序。
示例:key=12360324711202065,哈希表长度为1000,数组下标都是三位数 000, 001…999,所以应该把关键字分成3位一段,舍弃最低的两位65
1 2 3 1 2 3
6 0 3 3 0 6
2 4 7 2 4 7
1 1 2 2 1 1
+ 0 2 0 + 0 2 0
———————————————— ——————————————————
1 1 0 5 9 0 7
特殊操作 直接叠加
直接叠加得到 907,正好3位,得到哈希地址 907;特殊操作叠加得到 1105,结果只保留3位,舍弃最高位的1,得到哈希地址 105。
4、随机乘数法
hash(key) = n * random(key)
n是哈希表长度,采用随机函数生成一个 0~1 的随机小数,乘以哈希表长度分散到哈希表上,对结果取整得到哈希地址(要使用的数组下标)。
5、直接定址法
hash(key) = key + c
在key的基础上与常数进行数学运算。示例:哈希表长度10000,下标范围0~9999,要存储的元素是业务编号,编号范围1~10000,则可以使用 hash(key) = key - 1。
解析哈希冲突的常见方式
1、开放地址法
也叫作开放定址法,当关键字key的哈希地址 p=H(key) 出现冲突时,以p为基础生成另一个哈希地址p1,如果p1仍然冲突,再以p为基础生成另一个哈希地址p2…直到找出一个不冲突的哈希地址pi,将元素存入其中。这种方式有一个通用公式
Hi = ( H(key) + di ) % n
n是哈希表的表长(数组长度),di是增量序列,i=1, 2…n。di的取值有3种方式
- 线性探测再散列:逐渐+1,
di = 1 , 2 , 3 , … , n-1
- 平方探测再散列:也叫作二次探测再散列,二次指的是二次方,
di=1^2,-1^2,2^2,-2^2,…,k^2,-k^2
( k<=n/2 ) - 随机探测再散列:di 是一组伪随机数列
2、拉链法
也叫作链地址法、链表法,将哈希地址相同的元素存储在一个链表中,将链表的头指针直接存储在哈希表(数组)中。
优点:实现简单,且链表中的节点使用的内存空间是动态申请的,避免了内存空间的浪费,十分适合添加元素前不好确定元素个数的情况。HashMap即使用此种方式解决哈希冲突。
3、再哈希法
也叫作再散列法,同时构建多个哈希函数,使用第一个哈希函数计算得到的地址存在冲突时,使用第二个哈希函数计算,以此类推,直到计算得到的地址不发生冲突。
缺点:可能需要进行多次哈希计算,增加了计算的时间开销。
4、使用公共溢出区
把哈希表(数组)作为基本表,建立一个公共溢出区,把和基本表中冲突的元素都存储在溢出区中。