课程咨询 :15265420612 QQ:2720475033

潍坊java培训 > 达内新闻 > 高性能场景下,HashMap的优化使用建议
  • 高性能场景下,HashMap的优化使用建议

    发布:潍坊java培训机构      来源:潍坊java培训机构      时间:2016-11-04

  • 潍坊java培训机构小编给大家分析HashMap 在JDK 7 与 JDK8 下的差别

    顺便理一下HashMap.get(Object key)的几个关键步骤,作为后面讨论的基础。

    1.1 获取key的HashCode并二次加工

    因为对原Key的hashCode质量没信心,怕会存在大量冲突,HashMap进行了二次加工。

    JDK7的做法:

    h ^= (h >>> 20) ^ (h >>> 12);

    return h ^ (h >>> 7) ^ (h >>> 4);

    JDK8 因为对自己改造过的哈希大量冲突时的红黑树有信心,所以简单一些,只是把高16位异或下来。

    return h ^ (h >>> 16);

    所以即使Key比较均匀无哈希冲突,JDK8也比JDK7略快的原因大概于此。

    顺便科普一下,Integer的HashCode就是自己,Long要把高32位异或下来变成int, String则是循环累计结果*31+下一个字符,不过因为String是不可变对象,所以生成完一次就会自己cache起来。

    1.2 落桶

    index = hash & (array.length-1);

    桶数组大小是2的指数的好处,通过一次&就够了,而不是代价稍大的取模。

    1.3 最后选择Entry

    判断Entry是否符合,都是首先哈希值要相等,但因为哈希值不是唯一的,所以还要对比key是否相等,最好是同一个对象,能用==对比,否则要走equals()。 比如String,如果不是同一个对象,equals()起来要一个个字符做比较也是挺累的。

    if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

    return e.value;

    更累的是存在哈希冲突的情况,比如两个哈希值取模后落在同一个桶上,或者两条不同的key有相同的哈希值。

    JDK7的做法是建一条链表,后插入的元素在上面,一个个地执行上面的判断。

    而JDK8则在链表长度达到8,而且桶数量达到64时,建一棵红黑树,解决严重冲突时的性能问题。

    2. 很多人忽视的加载因子Load Factor

    加载因子存在的原因,还是因为减缓哈希冲突,如果初始桶为16,等到满16个元素才扩容,某些桶里可能就有不止一个元素了。所以加载因子默认为0.75,也就是说大小为16的HashMap,到了第13个元素,就会扩容成32。

    2.1 考虑加载因子地设定初始大小

    相比扩容时只是System.arraycopy()的ArrayList,HashMap扩容的代价其实蛮大的,首先,要生成一个新的桶数组,然后要把所有元素都重新Hash落桶一次,几乎等于重新执行了一次所有元素的put。

    所以如果你心目中有明确的Map 大小,设定时一定要考虑加载因子的存在。

    Map map = new HashMap(srcMap.size())这样的写法肯定是不对的,有25%的可能会遇上扩容。

    Thrift里的做法比较粗暴, Map map = new HashMap( 2* srcMap.size()), 直接两倍又有点浪费空间。

    Guava的做法则是加上如下计算

    (int) ((float) expectedSize / 0.75F + 1.0F);

    2.2 减小加载因子

    在构造函数里,设定加载因子是0.5甚至0.25。

    如果你的Map是一个长期存在而不是每次动态生成的,而里面的key又是没法预估的,那可以适当加大初始大小,同时减少加载因子,降低冲突的机率。毕竟如果是长期存在的map,浪费点数组大小不算啥,降低冲突概率,减少比较的次数更重要。

    3. Key的设计

    对于String型的Key,如果无法保证无冲突而且能用==来对比,那就尽量搞短点,否则一个个字符的equals还是花时间的。

    甚至,对于已知的预定义Key,可以自己试着放一下,看冲不冲突。比如,像”a1”,”a2”,”a3” 这种,hashCode是个小数字递增,绝对是不冲突的:)

    4. EnumMap

    对于上面的问题,有些同学可能会很冲动的想,这么麻烦,我还是换回用数组,然后用常量来定义一些下标算了。其实不用自己来,EnumMap就是可读性与性能俱佳的实现。

    EnumMap的原理是,在构造函数里要传入枚举类,那它就构建一个与枚举的所有值等大的数组,按Enum. ordinal()下标来访问数组,不就是你刚才想做的事情么?

    美中不足的是,因为要实现Map接口,而 V get(Object key)中key是Object而不是泛型K,所以安全起见,EnumMap每次访问都要先对Key进行类型判断。在JMC里录得不低的采样命中频率。

    所以也可以自己再port一个类出来,不实现Map接口,或者自己增加fastGet(),fastPut()的函数。

    5. IntObjectHashMap

    Netty以及其他FastUtils之类的原始类型map,都支持key是int或 long。但两者的区别并不仅仅在于int 换 Integer的那点空间,而是整个存储结构和Hash冲突的解决方法都不一样。

    HashMap的结构是 Node[] table; Node 下面有Hash,Key,Value,Next四个属性。

    而IntObjectHashMap的结构是int[] keys 和 Object[] values.

    在插入时,同样把int先取模落桶,如果遇到冲突,则不采样HashMap的链地址法,而是用开放地址法(线性探测法)index+1找下一个空桶,最后在keys[index],values[index]中分别记录。在查找时也是先落桶,然后在key[index++]中逐个比较key。

    所以,对比整个数据结构,省的不止是int vs Integer,还有每个Node的内容。

    而性能嘛,IntObjectHashMap还是稳赢一点的,随便测了几种场景,耗时至少都有24ms vs 28ms的样子,好的时候甚至快1/3。

    更多资讯扫请描下方二维码!

    潍坊java培训机构

上一篇:北航毕业生参加达内UI培训,从求职屡屡碰壁到赢得10k高薪

下一篇:从现在起,培养几个获益终生的思维习惯

最新开班日期  |  更多

Java--零基础全日制班

Java--零基础全日制班

开班日期:2月15日

Java--大数据周末班

Java--大数据周末班

开班日期:2月15日

Java--大数据全日制班

Java--大数据全日制班

开班日期:2月15日

Java--零基础周末班

Java--零基础周末班

开班日期:2月15日

  • 地址:潍坊中心地址:潍坊市奎文区东风东街299号建行大厦五层 电话:0536-8222150
    临沂中心地址:临沂市兰山区红旗路1号苏宁易购四楼 电话:0539-7205599
    淄博市张店区金晶大道68号华润大厦25层 电话:18005330180
  • 课程培训电话:15265420612 QQ:2720475033     全国服务监督电话:400-827-0010
  • 服务邮箱 ts@tedu.cn
  • 2001-2016 达内时代科技集团有限公司 版权所有 京ICP证8000853号-56