您的位置:首页 > 其它

哈希表实现原理

2016-07-14 16:22 507 查看
1.hash

哈希表作为一种高效的存储结构,可以大大降低查询和存储的时间,其核心理念为空间换时间,消耗一部分多余的内存来换取较高的性能,在当下电脑内存越来越大的趋势下,十分划算。

哈希表又称为散列表,可以分为闭散列和开散列两种,这里只研究闭散列。

2.实现原理

哈希表的底层为一个长度较长的数组,通过将存放的各个元素进行一系列运算而转化为一串hash值,再通过相同的规则与数组的下标进行对应。但是,这并不能保证元素对应的下标唯一,可能存在两个元素运算后的hash值相同,从而指定到相同的下标(也会存在不同的hash值对应相同的数组下标),此时需要对这种冲突进行处理。

故哈希表的实现主要为“建立下标对应”和“冲突处理”两部分。

3.hash值的实现

hash值的计算应该尽量实现少重复的特性,下面以String的HashCode方法为例:

public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;

for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
与素数相乘是使用较多的处理方法,并且相乘的素数越大,重复的概率越小,是一种高效的处理手段。

4.建立数组下标对应关系

一般采用的方法有两种:

    1.取余法

    length为数组的长度

    利用hash%length建立对应关系,为了降低余数的重复性,length为素数,这样可以充分运用数组的每个下标,且不会越界。

    2.&运算

    将length设为2的倍数,可以得到length-1的二进制所有位都为1,所以hash&(length-1)可以运用数组的每一个下标,且不会越界

5.冲突处理

哈希表的设计从一直在避免重复,但重复的情况依旧会发生,这里介绍两种处理方法:

1.后退法

此方法必须在节点中添加一个next属性,将连续的有效节点变成一个链表,同时重复的元素会占用本属于别的元素的下标,而别的元素在被占用下标后又只能继续占用其他下标,这会使查询的效率大大降低,此设计模式要求哈希表中的冲突维持在较低水平,而且冲突上升会非常快。在移除一个节点后,需要将前节点的next属性修改(前节点非空时),否则会导致链表断裂,数据丢失。查询元素时,便可以使用next属性来遍历,直至空节点。

下面为后退法设计的一个小案例 

</pre><pre name="code" class="java">public void addEntry(K key, V value) {
int hash = hash(key);//计算hash值,不具体写了,怎么实现可以自己定,重复率较低就行
int i = indexFor(hash,length);//获取对应的数组下标,不具体写了,取余或&都可以
Entry<K,V> e;
for (; e != null; i++) {
e = table[i];
if(e.hash==hash && e.key==key){//若该key已存在,则替换value
e.value=value;
return null;
}
}
e=new Entry();若未找到该key,则在空节点处创建包含该键值对的节点
if(null!=table[i-1]){
table[i-1].next=e;//此方法必须将有效节点连接起来
}
e.hash=hash;
e.key=key;
e.value=value;
e.next=table[i+1];
}2.链表法
当两个不同的元素下标冲突时,将这两个下标合成一个链表存放于同一个下标下。HashMap便运用了此方法,下面为HashMap的源码

void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);//若原来该下标有值,将以链表的形式在该下标位存放多个元素,并size++
size++;
}此方法的好处是不会影响别的下标,效率上高于第一种方法。

6.总结

根据对存储模式的分析,可以得到这样的结论:在容量使用率较低时,哈希表的查询和存储会非常的快(很少有多个元素的计算下标值相同,基本不需要冲突处理),而当容量使用率较高时,冲突出现的概率大大增加,查询的效率会大打折扣。据统计,使用率到打80%时,查询效率会开始有明显的下降,而到打90%时,查询效率已经非常低了,会有大量的冲突存在。所以一般的哈希表都会在使用率达到一定值后对哈希表进行扩容,并将各个元素存入新数组,各个元素也将获得新的下标,由于使用率的下降,冲突会大大降低,使哈希表的查询和存储性能一直维持在较高水平。但是这会造成一定的内存浪费,例如hashmap的加载因子(使用率)为75%(容量使用率超过75%就会进行扩容),这使得有25%的内存一直被浪费,这便是空间换时间的设计理念。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: