哈希表与HashMap

哈希表与HashMap
1.哈希表是什么?
哈希表通过计算一个以记录的关键字为自变量的函数(哈希函数)来得到该记录的储存地址。
2.哈希冲突是什么?
哈希冲突:对于某个哈希函数H和两个关键字K1和K2,如果K1不等于K2,而H(K1)=H(K2),则称为哈希冲突。
通俗来讲,就是两个关键字映射到了同一个地址上,产生了冲突。
我们也称K1和K2为同义词。
注意:理想情况是哈希地址平均的分散,这样会减少冲突。但冲突无法完全避免,只能减少。因为哈希函数是从关键字集合到地址集合的映像。简单来说,如果关键字和地址是1对1,那么就不会有冲突,然而哈希函数是一种压缩映像函数,就是为了减少存储空间的,所以1对1没有意义。
3.如何处理哈希冲突?
解决冲突,就是给冲突的关键字再找一个空的地址来存放。那么有下面几种处理哈希冲突的方式:
A.开放定址法
Hi=(H(key)+di )%m i=1,2,...,k (k<=m-1)
H(key)为哈希函数,m为哈希表表长,di为增量序列。
增量序列有以下几种:
1.di=1,2,3...,m-1,线性探测再散列 
2.di=1²,-1²,2²,-2²,...,±k²(k<= m/2),二次探测再散列 
3.di=伪随机数序列,称为随机探测再散列
 
最简单的是线性探测再散列。
缺点:1.溢出需要单独处理。2.容易产生聚集现象。
二次探测再散列和随机探测再散列可以减少聚集现象。
B.链地址法
也叫拉链法,即在查找表的每个记录中增加一个链域,其中存放下一个具有相同哈希函数值的记录的存储地址。利用链域,就把若干个发生冲突的记录链接在一个链表内。当链域的值为null,表示已没有后继记录了,因此,对于发生冲突时的查找和插入操作就和线性表一样了。
C.再哈希法
Hi=RHi(key)  (i=1,2,...,k)
RHi均是不同的哈希函数。即在同义词发生地址冲突时计算另一个哈希函数地址,直到冲突不再发生。这种方法不易产生聚集现象,但增加了计算时间。
D.建立公共溢出区
只要发生冲突,都填入到公共溢出区中。
4.什么是装填因子?
α=表中装入的记录数/哈希表的长度。
α标志着哈希表的装满成都。α越大,表中填入的记录越多,越容易发生冲突,查找给定值需要比较的关键字就越多。
 
 
了解了哈希表的知识点,那么理解我们常用的HashMap的实现原理就很简单了。
 
HashMap也采用哈希存储方式,因此也会产生哈希冲突,HashMap的处理冲突方法就是上面的B.链地址法,通过建立一个链域,来存放冲突的关键字指向的地址。
因此HashMap的源码原理通过猜测就可以得出:
首先有个哈希函数,来计算哈希地址,也就是map.put方法,一个put的操作。
如果这个哈希地址没值,那么就存进去,如果有,就是产生了哈希冲突,就在这个哈希地址链域增加一个指针,指向产生冲突的关键字的哈希地址。
我们查看HashMap的源码就会发现,原理就是我们猜测的大致一样。
HashMap的底层由Entry对象的数组存储,通过hash算法计算在数组中的存储位置,
put方法,根据equals决定其在数组位置上的链域中的存储位置。
put方法有两种处理:1.如果key相同,覆盖。2.如果key不同(哈希冲突),将当前的key-value放入链表中。
 
HashMap的哈希函数如下:
static int hash(int h) {
     h ^= (h >>> 20) ^ (h >>> 12);
     return h ^ (h >>> 7) ^ (h >>> 4);
 }

 

Hash的put方法如下
public V put(K key, V value) {
    // HashMap允许存放null键和null值。
    // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。
    if (key == null)
        return putForNullKey(value);
    // 根据key的keyCode重新计算hash值。
    int hash = hash(key.hashCode());
    // 搜索指定hash值在对应table中的索引。
    int i = indexFor(hash, table.length);
    // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            // 如果发现已有该键值,则存储新的值,并返回原始值
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    // 如果i索引处的Entry为null,表明此处还没有Entry。
    modCount++;
    // 将key、value添加到i索引处。
    addEntry(hash, key, value, i);
    return null;
}

 

Hash的get方法如下
public V get(Object key) {
    if (key == null)
        return getForNullKey();
    int hash = hash(key.hashCode());
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
        e != null;
        e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
            return e.value;
    }
    return null;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值