HashMap之hash方法

 

思路

什么是hash?常见的实现又什么?什么是hash碰撞?怎么衡量一个hash函数的好坏?常见的hash碰撞解决方案有哪些?HashMap or HashTable的hash方法基本原理是什么?jdk7/8中HashMap碰撞解决方案的差异?为什么?
 

概念

任意长度的输入通过散列算法,变换成固定长度的输出,称散列值。
 


常见的Hash函数

 

Hash碰撞

不同的输入经过散列算法得到的散列值相同,这种情况称为Hash碰撞。
 

衡量Hash函数好坏的指标

  • 发生碰撞的概念越低越好;
  • 发生碰撞的解决方案。
 

常见的碰撞解决方案

  • 开放定址法:
    • 开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
  • 链地址法(拉链法)
    • 将哈希表的每个单元作为链表的头结点,所有哈希地址为i的元素构成一个同义词链表。即发生冲突时就把该关键字链在以该单元为头结点的链表的尾部。
  • 再哈希法
    • 当哈希地址发生冲突用其他的函数计算另一个哈希函数地址,直到冲突不再产生为止。
  • 建立公共溢出区
 
 

hash方法的主要调用者

主要是删除和添加的方法。(HashTable、ConcurrentHashMap同理。
jdk1.7下:
 
 

源码解析

同一个版本的Jdk中,HashMap、HashTable以及ConcurrentHashMap里面的hash方法的实现是不同的。再不同的版本的JDK中(Java7 和 Java8)中也是有区别的。
 

HashMap的hash函数基本原理

我们可以通过hashCode()返回的数值(Key的数据类型只能为引用类型),对HashMap or Hashtable的容量进行取模即可。
 
具体实现上,由两个方法int hash(Object k)int indexFor(int h, int length)来实现。但是考虑到效率等问题,HashMap的实现会稍微复杂一点。
 
 

HashMap In Java 7(hash方法)

final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

static int indexFor(int h, int length) {
    return h & (length-1);
}
 
return h & (length-1);是什么意思呢?其实,他就是取模。Java之所有使用位运算(&)来代替取模运算(%),最主要的考虑就是效率。位运算(&)效率要比代替取模运算(%)高很多,主要原因是位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。
 
 

HashMap In Java 8(冲突解决)

碰撞解决方案的差异

在Java 8 之前,HashMap和其他基于map的类都是通过链地址法解决冲突,它们使用单向链表来存储相同索引值的元素。
在最坏的情况下,这种方式会将HashMap的get方法的性能从O(1)降低到O(n)
如果恶意程序知道我们用的是Hash算法,则在纯链表情况下,它能够发送大量请求导致哈希碰撞,然后不停访问这些key导致HashMap忙于进行线性查找,最终陷入瘫痪,即形成了拒绝服务攻击(DoS)。
 
Java 8中使用平衡树(当链表长度超过8的时候切换成红黑树)来替代链表存储冲突的元素。这意味着我们可以将最坏情况下的性能从O(n)提高到O(logn)
 

 
补充
在Java 8的HashTable中,已经不在有hash方法了。但是哈希的操作还是在的,比如在put方法中就有如下实现)
 

ConcurrentHashMap In Java 8

Java 8 里面的求hash的方法从hash改为了spread。实现方式如下:

static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}
 

Java 8的ConcurrentHashMap同样是通过Key的哈希值与数组长度取模确定该Key在数组中的索引。同样为了避免不太好的Key的hashCode设计,它通过如下方法计算得到Key的最终哈希值。不同的是,Java 8的ConcurrentHashMap作者认为引入红黑树后,即使哈希冲突比较严重,寻址效率也足够高,所以作者并未在哈希值的计算上做过多设计,只是将Key的hashCode值与其高16位作异或并保证最高位为0(从而保证最终结果为正整数)。

 
 
 
 
 

猜你喜欢

转载自blog.csdn.net/qq_38974073/article/details/109587830