龙空技术网

四、JDK1.7中HashMap哈希冲突解决方案-阿里面试经

花椒笔记 132

前言:

现在同学们对“哈希表存储中解决冲突的基本方法有哪些”都比较着重,我们都想要了解一些“哈希表存储中解决冲突的基本方法有哪些”的相关文章。那么小编同时在网摘上搜集了一些关于“哈希表存储中解决冲突的基本方法有哪些””的相关内容,希望我们能喜欢,兄弟们一起来了解一下吧!

导读

前面文章一、深入理解-Java集合初篇 中我们对Java的集合体系进行一个简单的分析介绍,上两篇文章二、Jdk1.7和1.8中HashMap数据结构及源码分析 、二、JDK1.7和1.8HashMap数据结构及源码分析-续 中我们分别对JDK1.7和JDK1.8中HashMap的数据结构、主要声明变量、构造函数、HashMap的put操作方法做了深入的讲解和源码分析。三、深入理解Java中的HashMap「网易面试快答」 文章中主要针对面试中常见的文件进行简单解答。

本篇文章我们将要对JDK1.7中HashMap的哈希冲突及减少哈希冲突的解决方案做详细的介绍,并通过源码加深大家的理解。

如果大家在面试中针对Java集合或者Java中的HashMap大家还有什么疑问或者其他问题,可以评论区告诉我。

简单介绍

JDK1.7---》哈希表,链表

JDK1.8---》哈希表,链表,红黑树--- JDK1.8之后,当链表长度超过8使用红黑树。

非线程安全

0.75的负载因子,扩容必须为原来的两倍。

默认大小为16,传入的初始大小必须为2的幂次方的值,如果不为也会变为2的幂次方的值。

根据HashCode存储数据。

JDK1.7的哈希冲突解决方案1.配置threshold:jdk.map.althashing.threshold(阀值-门槛)

配置改变hash冲突的门槛

源码:

/** * The default threshold of map capacity above which alternative hashing is * used for String keys. Alternative hashing reduces the incidence of * collisions due to weak hash code calculation for String keys. * <p/> * This value may be overridden by defining the system property * {@code jdk.map.althashing.threshold}. A property value of {@code 1} * forces alternative hashing to be used at all times whereas * {@code -1} value ensures that alternative hashing is never used. */static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

-Djdk.map.althashing.threshold = -1: 表示不做优化(不配置这个值作用一样)

-Djdk.map.althashing.threshold < 0: 报错

-Djdk.map.althashing.threshold = 1: 表示总是启用随机HashSeed

-Djdk.map.althashing.threshold >= 0 : 表示hashMap内部的数组长度超过该值了就使用随机HashSeed,降低碰撞

如果 配置该值为-1,表示不做hash冲突的优化;

如果 配置该值小于0,则报错;

如果 配置该值为1 则表示总是使用一个随机值(哈希因子hashseed)对hash冲突.

如果 配置该值大于等于0 表示当HashMap中数组长度超过该值的时候就使用随机值(哈希因子hashseed)来降低哈希冲突的可能性。

2使用一个私有的静态内部类Holder加载虚拟机引导之后才被初始化的值。

使用私有的静态内部类Holder加载上一步配置的Jdk.map.althashing.threshold。

源码:

/** * The default threshold of map capacity above which alternative hashing is * used for String keys. Alternative hashing reduces the incidence of * collisions due to weak hash code calculation for String keys. * <p/> * This value may be overridden by defining the system property * {@code jdk.map.althashing.threshold}. A property value of {@code 1} * forces alternative hashing to be used at all times whereas * {@code -1} value ensures that alternative hashing is never used. */static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;/** * holds values which can't be initialized until after VM is booted. */private static class Holder {    /**     * Table capacity above which to switch to use alternative hashing.     */    static final int ALTERNATIVE_HASHING_THRESHOLD;    static {        String altThreshold = java.security.AccessController.doPrivileged(            new sun.security.action.GetPropertyAction(                "jdk.map.althashing.threshold"));        int threshold;        try {            threshold = (null != altThreshold)                    ? Integer.parseInt(altThreshold)                    : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;            // disable alternative hashing if -1            if (threshold == -1) {                threshold = Integer.MAX_VALUE;            }            if (threshold < 0) {                throw new IllegalArgumentException("value must be positive integer.");            }        } catch(IllegalArgumentException failed) {            throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);        }        ALTERNATIVE_HASHING_THRESHOLD = threshold;    }}/** * A randomizing value associated with this instance that is applied to * hash code of keys to make hash collisions harder to find. If 0 then * alternative hashing is disabled. */transient int hashSeed = 0;

源码解读:

1、static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

如果在上一步中没有配置“jdk.map.althashing.threshold”,则使用该值表示是否对HashMap中的哈希冲突做干扰。该值的默认值是Integer的最大值。表示只有当HashMap中数组容量达到Integer的最大值时候才会做哈希冲突的干扰。设置这个值这么大,其实就是不做哈希冲突干扰。

2、在私有的静态内部类Holder中做判断判断。

①加载(jdk.map.althashing.threshold)配置的altThreshold;

String altThreshold = java.security.AccessController.doPrivileged(    new sun.security.action.GetPropertyAction(        "jdk.map.althashing.threshold"));

②如果从配置中加载到的altThreshold不为空,则把threshold赋值为加载到的altThreshold;

如果没有配置该altThreshold则使用默认的ALTERNATIVE_HASHING_THRESHOLD_DEFAULT,即Integer.MAX_VALUE;

threshold = (null != altThreshold)        ? Integer.parseInt(altThreshold)        : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;

③如果在JDK中配置的jdk.map.althashing.threshold为默认值-1,则把threshold设置为Integer.MAX_VALUE;

// disable alternative hashing if -1if (threshold == -1) {    threshold = Integer.MAX_VALUE;}

④.如果在JDK中配置的jdk.map.althashing.threshold值小于0,则报错,表示该值不是一个有效值。

if (threshold < 0) {    throw new IllegalArgumentException("value must be positive integer.");}

⑤.把经过计算的threshold的值赋值给“ALTERNATIVE_HASHING_THRESHOLD”.

也就是说最终是否对哈希冲突做干扰,或者在什么情况进行干扰是存储在“ALTERNATIVE_HASHING_THRESHOLD”中的。

ALTERNATIVE_HASHING_THRESHOLD = threshold;

⑥.解决哈希冲突的干扰因子。根据上一步中计算的配置“ALTERNATIVE_HASHING_THRESHOLD”判断是否启用该干扰因子。

/** * A randomizing value associated with this instance that is applied to * hash code of keys to make hash collisions harder to find. If 0 then * alternative hashing is disabled. */transient int hashSeed = 0;

从代码看出jdk.map.althashing.threshold这个变量设置的值最终会存放在静态常量ALTERNATIVE_HASHING_THRESHOLD

3.根据初始化的HashMap容量大小,决定干扰因子的值。

源码:

/**初始化哈希干扰的掩码值,我们把它的设置延迟到了真正使用它的时候。 * Initialize the hashing mask value. We defer initialization until we * really need it. */final boolean initHashSeedAsNeeded(int capacity) {   /**判断是否开启了hash干扰。如果hashseed 等于 0,则currentAltHashing = false;如果hashseed 不等于 0 ,则currentAltHashing = true;**/     boolean currentAltHashing = hashSeed != 0;/** 判断是否使用为干扰1.如果当前HashMap数组容量的大小等于jdk配置    中“jdk.map.althashing.threshold”的值时候,    且VM .isbooted 为true 时候,userAltHashing 为true;2.如果当前HashMap数组容量的小于jdk配置    中“jdk.map.althashing.threshold”的值时候,    且VM .isbooted 为true 时候,userAltHashing 为false;3. 如果当前HashMap数组容量的大小等于jdk配置    中“jdk.map.althashing.threshold”的值时候,    且VM .isbooted 为false 时候,userAltHashing 为false;4. 如果当前HashMap数组容量的小于jdk配置    中“jdk.map.althashing.threshold”的值时候,    且VM .isbooted 为false 时候,userAltHashing 为false;**/    boolean useAltHashing = sun.misc.VM.isBooted() &&            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);/** 对以上的两个值进行“异或”处理异或的结果为 两个值相同为0,两个值不同为1.也就是说 currentAltHashing 为true时且useAltHashing也为true时,或者 currentAltHashing 为false时且useAltHashing也为false时switching的结果为 true.其他情况时候,switching为false.**/    boolean switching = currentAltHashing ^ useAltHashing;    if (switching) {/**如果switching为true ,设置hashSeed(干扰因子)的值。如果useAltHashing的值为true,则随机一个干扰值给HashSeed。否则赋值为0.**/         hashSeed = useAltHashing            ? sun.misc.Hashing.randomHashSeed(this)            : 0;    }//返回是否启用干扰因子    return switching;}

源码解读:

当hashMap扩大容量时,都是调用该方法。从代码可以看出,当数组容量超过,我们设定的值ALTERNATIVE_HASHING_THRESHOLD且是vm booted,同时 hashSeed==0的时候,hashSeed的值就是用随机量,而不是固定的等于0。这样就能降低碰撞,就能降低演化成链表概率。

代码具体过程:

当 hashSeed==0 则 currentAltHashing=false当 capacity < Holder.ALTERNATIVE_HASHING_THRESHOLD 则currentAltHashing =false结果:switching=false 当 hashSeed==0 则 currentAltHashing=false当 capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD  则 currentAltHashing =true结果:switching=true  当 hashSeed !=0 则 currentAltHashing=true当 capacity < Holder.ALTERNATIVE_HASHING_THRESHOLD  则 currentAltHashing =false结果:当 switching=true 当 hashSeed !=0 则 currentAltHashing=true当 capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD  则 currentAltHashing =true结果:switching=false

回头再看代码,发现很巧妙

结论:

①.如果不配置“jdk.map.althashing.threshold”,则HashMap中的私有静态内部类中的’ALTERNATIVE_HASHING_THRESHOLD‘的值为Integer.MAX_VALUE,且HashSeed默认值为0,则switching的值永远为false,也就永远不会改变干扰因子(HashSeed)的值。

②.如果配置了“jdk.map.althashing.threshold”,则会根据当前HashMap中的数组容量动态的变更HashSeed的值,以便于引入HashSeed降低哈希冲突。

-Djdk.map.althashing.threshold=-1:表示不做优化(不配置这个值作用一样)

-Djdk.map.althashing.threshold<0:报错

-Djdk.map.althashing.threshold=1:表示总是启用随机HashSeed

-Djdk.map.althashing.threshold>=0:便是hashMap内部的数组长度超过该值了就使用随机HashSeed,降低碰撞

4.获取key的Hash值,位干扰。

源码:

/** * Retrieve object hash code and applies a supplemental hash function to the * result hash, which defends against poor quality hash functions.  This is * critical because HashMap uses power-of-two length hash tables, that * otherwise encounter collisions for hashCodes that do not differ * in lower bits. Note: Null keys always map to hash 0, thus index 0. */final int hash(Object k) {/** 获取哈希干扰因子,该因子会跟根据HashMap的容量进行变更变更情况根据上一步的“final boolean initHashSeedAsNeeded(int capacity)”方法动态变更**/     int h = hashSeed;//如果为干扰因子不为0,且传入的key类型为String,则使用特定的算法(sun.misc.Hashing.stringHash32((String) k))对该key进行hash计算。并返回    if (0 != h && k instanceof String) {        return sun.misc.Hashing.stringHash32((String) k);    }//如果哈希干扰因子为0 或者 k的类型不为String则使用异或操作变更key的hashcode    h ^= k.hashCode();//为了减少Hash冲突出现次数进行必要的位干扰,默认负载因子是8.    // This function ensures that hashCodes that differ only by    // constant multiples at each bit position have a bounded    // number of collisions (approximately 8 at default load factor).    h ^= (h >>> 20) ^ (h >>> 12);    return h ^ (h >>> 7) ^ (h >>> 4);}

源码解读:

可以看出生成的hash值和hashSeed 这个值有着紧密的关系,但是这个值默认是0。也就是说不管HashMap存多少数据,hashSeed 都是不会变的,可以看出随着hashMap 的容量增大,hash碰撞的概率增大的可能性也就增大。如果hash值,碰撞很高的话,那么hashMap逐渐演化成链表,性能就急剧下降。

在hash(Object k)中有这么一段位运算的代码:

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

看起来既简单又深奥的样子,让我们来看看这段代码隐藏的东西吧。

k.hashCode()函数调用的是key键值类型自带的哈希函数,由于不同的对象其hashCode()有可能相同,所以需对hashCode()再次哈希,以降低相同率。

接下来的一串与运算和异或运算,称之为“扰动函数”,扰动的核心思想在于使计算出来的值在保留原有相关特性的基础上,增加其值的不确定性,从而降低冲突的概率。不同的版本实现的方式不一样,但其根本思想是一致的。

这里的具体实现方式是如何保证的呢?笔者功力浅薄,暂时还没有答案,如果有朋友知道的话可以交流。但是,“扰动函数”的核心思想一定要明白。

往期文章链接

Java集合

一、深入理解-Java集合初篇

二、Jdk1.7和1.8中HashMap数据结构及源码分析

二、JDK1.7和1.8HashMap数据结构及源码分析-续

三、深入理解Java中的HashMap「网易面试快答」

Java-IO体系

一、JAVA IO/NIO体系介绍

二、网络IO原理-创建ServerSocket-彻底弄懂IO

三、JAVA中ServerSocket调用Linux系统内核

四、「大厂职员教你」IO进化过程之BIO

五、「大厂职员教你」Java-IO进化过程之NIO

六、Selector实现Netty中Reactor单线程模型

七、Selector实现Netty中Reactor主从模型

八、Netty入门服务端代码

九、IO进化过程之EVENT(EPOLL-事件驱动异步模型)

如需了解更多更详细内容也可关注本人CSDN博客:不吃_花椒

Java集合还需要学习的内容

标签: #哈希表存储中解决冲突的基本方法有哪些