• Java集合之HashMap


    关于HashMap的特点

    1、HashMap是非线程安全的。

    也就是在多线程下,使用HashMap不能保证存储的数据的正确性,也就是会出现多个线程同时操作一个数据,从而导致数据与设想的不一致。

    2、HashMap初始化容量为16,每次扩容都是原来容量的两倍,扩容因子为0.75,底层是Hash表(JDK1.8之前是数组+链表,JDK1.8开始变为数组+链表+红黑树)。

    1)也就是当元素的数量达到容器的百分之75时就会扩容。而为什么是0.75而不是其他的 ?

    首先,0.75这个数值是研究人员利用统计学得到的最优解,当低于0.75,就会导致频繁扩容,扩容会降低效率;其次,也会导致空间过多浪费,降低空间利用率;当高于0.75,就会增加Hash冲突的发生概率,那么链表的长度会增加,接着红黑树也会变得更复杂,从而导致查询效率降低,也就是时间利用率降低。

    2)为什么初始化容量为16?

      HashMap作为一种数据结构,元素在put的过程中需要进行hash运算,目的是计算出该元素存放在hashMap中的具体位置。

      hash运算的过程其实就是对目标元素的Key进行hashcode,再对Map的容量进行取模,而JDK 的工程师为了提升取模的效率,使用位运算代替了取模运算,这就要求Map的容量一定得是2的幂。

       

     X % 2^n = X & (2^n – 1)

      而作为默认容量,太大和太小都不合适,太小了就有可能频繁发生扩容,影响效率。太大了又浪费空间,不划算,所以16就作为一个比较合适的经验值被采用了。

       

      为了保证任何情况下Map的容量都是2的幂,HashMap在两个地方都做了限制。

      首先是,如果用户制定了初始容量,那么HashMap会计算出比该数大的第一个2的幂作为初始容量。

      另外,在扩容的时候,也是进行成倍的扩容,即4变成8,8变成16。

    3)为什么建议设置hashmap的初始容量,设置多少合适?

    为什么设置初始容量?

    HashMap有扩容机制,就是当达到扩容条件时会进行扩容。HashMap的扩容条件就是当HashMap中的元素个数(size)达到或超过临界值(threshold)时就会自动扩容。在HashMap中,threshold = loadFactor * capacity,扩容是非常影响性能的。并且《阿里巴巴开发手册》中也是这么建议的:

    设置多少合适?

    有些人会自然想到,我准备塞多少个元素我就设置成多少呗。比如我准备塞7个元素,那就new HashMap(7),那么jdk就会为我们创建一个容量为8的 HashMap

    但是,这个值看似合理,实际上并非如此。因为HashMap在根据用户传入的capacity计算得到的默认容量,并没有考虑到loadFactor这个因素,只是简单机械的计算出第一个大约这个数字的2的幂。

    这么讲很多人可能不理解,请看下面:

    如果我们需要用HashMap存7个元素,按照上面的策略,初始大小就会设置为7,经过JDK处理之后,HashMap的容量会被设置成8,但是,这个HashMap在元素个数达到 8*0.75 = 6的时候就会进行一次扩容,这明显是我们不希望见到的,那么到底设置为多少合适?

    这里我们可以参考JDK8中putAll方法中的实现:

    经过计算 7/0.75 + 1 = 10,10经过JDK处理之后,会被设置成16,这样就能大大的减少扩容的几率。

    所以,我们可以认为,当我们明确知道HashMap中元素的个数的时候,把默认容量设置成 expectedSize / 0.75F + 1.0F 是一个在性能上相对好的选择。

    当然,这种方式缺点也很明显,就是会牺牲内存。但是,现在硬件技术发达,我们认为内存属于比较丰富的资源,所以使用空间换时间是比较好的方案。

    注:关于以上三点的回答,作者讲的比较粗略,详细的原理和过程感兴趣的读者可以自行查找资料)

    3、HashMap的链表转红黑树的过程

    相信很多小伙伴都记住了一句话,HashMap中,当链表长度达到8时就会将链表转为红黑树,我之前也是这么认为的,知道最近去翻看了HashMap的源代码,才发现这句话不正确。

    是当链表长度不小于8时,且数组长度不小于64时,此时才会将链表长度不小于8的链表转换为红黑树,大家来看看源码吧。

     但是它有什么用呢?

    当我们调用put方法时,底层其实调用了putVal方法,让我们来看看putVal方法

     接着我们来看看treeifyBin方法

     4、HashMap是无序且不重复的。

    如果大家看了第三点就会知道为什么是不重复的,这里讲的再详细一点。

    无序的:HashMap取值的时候是从数组位为0的链表开始顺序往下查询,接着再查找数组位为1......,存放数据时是直接映射到数组下标的,所以存数据的时候是无序的,而取数据时与存数据时的顺序不一致,那么取出来的数据当然是无序了。

    不重复的:之所不重复,是因为调用了key的hashcode方法和equals方法(关于hashcode和hash值我就不详讲了,有兴趣的小伙伴可以去看看源码),不同的key可能有同样的hash值,而相同的key一定会有相同的hash值,如果hash值相同,那么就会定位到同一个数组位,这时候就会调用equals方法,如果重写了equals方法,就会比较对象的内容是否相同,如果相同就会覆盖,没有重写equals方法就会继承Object类的equals方法,去比较地址,如果地址相同也会覆盖,既然都覆盖了,那么也就不会出现重复了。

  • 相关阅读:
    [ZT].Net中動態建立和調用WebServices的方法
    英文符号读法整理
    [ZT]SQL Server 的事务日志意外增大或充满的处理方法
    [ZT]Asp.net發布至英文服务器后出现的日期格式问题
    [ZT]如何取得客户端的Windows登录用户名?
    囧!一个盗版用户和微软客服的通话记录
    [轉帖]x.509证书在WCF中的应用(CS篇)
    [原創]關於VS“無法辨認的逸出序列”的錯誤分析和解決方法
    【原創】文件系統目錄文件快速複製轉移工具
    SQL2000/SQL2005導入導出存儲過程圖解
  • 原文地址:https://www.cnblogs.com/zz-newbie/p/15019811.html
Copyright © 2020-2023  润新知