【Java】散列

若知道一个数组中元素的索引,可以使用索引在O(1)时间内获得元素。若将map中值存储在数组当中,并使map的键通过一个函数映射到值的索引上的话,就能利用键快速获取值。以上,存储了具有键值对的对象的数组称为散列表,映射函数称为散列函数。散列是一种无需执行搜索即可通过从键得到的索引来获取值的技术。java中,map是一种使用散列实现的数据结构。

典型的散列函数首先将搜索键转换为一个称为散列码的整数值,再将散列码压缩为散列表的索引。

Java的根类Object具有hashcode方法,该方法返回一个散列码,默认是对象的内存地址。当equals方法被重写时,也应该重写hashCode方法,这样可以保证当两个对象equals为true时hashcode也相同。

基本数据类型的散列码:

byte,short,int以及char类型的搜索键,散列码即它们转为int后的值。

对于float类型的搜索键,使用Float.floatToIntBits(key)作为散列码。

对于long类型的搜索键,因为其有64位,所以会分为两部分,各32位并执行异或操作将两部分结合,这个过程称为折叠:

int hashcode = (int)(key^(key>>32));

对于double类型的搜索键,首先使用Double.doubleToLongBits方法转换为long值,再折叠。

字符串的散列码:

s_{0}\times b^{n-1}+s_{1}\times b^{n-2}+...+s_{n-1}

这里的s_{i}为s.charAt(i),b为某个正数。长字符串可能会导致溢出,但是Java会忽略算术溢出。应该选择合适的b值来最小化冲突,实验显示较好的取值有31,33,37,39,41。String类中采用的是31。

压缩散列码:

键的散列码可能会是一个很大的数值,从而超过散列表的索引范围,因此需将其缩小到适合范围。假设索引0-N-1,则

int index = hashcode % N;

 

对N的取值,一般会选择素数来保证索引均匀展开,但是当散列表很大时,选取一个大于最大索引的素数会很耗时。因此在HashMap中,将N设置为2的整数次幂,这样能够通过&操作把散列码压缩为散列表索引值,如下式,比直接用%(取余)操作快很多。此外,也避免取余操作中,当hashcode为负值导致索引值也为负值的的问题,即下式的值永远为正。

int index = hashcode & (N-1);

Hash冲突

Hash冲突即两个不同的键值映射到同一个索引上。通常有两种方法处理:开放地址法和分离链接法

1.开放地址法:冲突发生时,在散列表再找一个开放位置的过程。

常见的开放地址法有线性探测法,二次探测法和双重散列法

线性探测法:当发生冲突时,按顺序找到下一个可用位置。若在索引[k%N]发生冲突,则检查[(k+1)%N]是否可用以此类推。若检查到表的终点则返回表的起点继续检查,直到找到一个不冲突的位置。

查找散列表条目时,先按照散列函数获得键相对的索引,再比对索引处的对象的键是否与要查找的键相等,若不是,则按顺序找下一个位置直到找到。删除条目时,和查找一样先找到条目的位置,若找到,则在该位置放一个特殊的标记表示该位置可用。所以散列表中的每个单元可能有三个状态:被占,标记,可用。

缺点:易导致散列表中连续的单元组被占用。每个连续的单元组称为簇,当簇的大小增大时,可能会和其他簇合并,从而放慢查找时间。

二次探测法:若索引[k%N]产生冲突,二次探测法将从[(k+j^2)%N]开始检查,其中j=1,2,3,……。二次探测法避免了成簇问题,但是又二次成簇的问题,即产生冲突的键在寻找下一个可用位置时总是采用通用的探测序列。此外,线性探测法可以保证只要表不是满的,总能找到可用单元,二次探测法无法保证。

双重散列法:以上两种方法的探测序列都是对k加上一个增量来得到的,且增量独立于键。双重探测法运用了第二个散列函数来确定增量。探测序列索引为(k+j*h2(key))%N,这里的h2即第二个散列函数,j取值为0,1,2,3,……从而避免成簇问题。第二个散列函数的设计应该要能使探测序列覆盖整个散列表,且不能有0值,因为增量不能是0。

2.分离链接法:将产生冲突的键值对都放置在相同索引处,并使用“桶”来放置同位置的多个键值对。

“桶”可以采用ArrayList或者LinkedList实现。

装填因子和再散列

装填因子用于衡量一个散列表有多满,若溢出,则要复制原散列表的所有条目到新的更大的散列表中,这个过程称为再散列。

装填因子是现有条目数目与散列表大小的比例。对于开放地址法,需维持装填因子在0.5以下,对于分离链表法,维持在0.9以下。再散列的代价比较大,为了减小再散列的可能性,应该至少将散列表的大小翻倍。

HashMap采用了分离链接法处理冲突,用LinkeList实现桶,且装填因子为0.75。set也是使用散列技术实现的。HashSet是基于HashMap实现的,将元素保存于map的key中,而value使用一个static final的Object对象标识。插入新元素时通过判断HashMap中是否有key与新元素相等,若无则添加,若有则跳过,以此保证不重复性。相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值