长文深度解读HashMap

主要静态变量

 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; static final float DEFAULT_LOAD_FACTOR = 0.75f; static final int TREEIFY_THRESHOLD = 8; static final int UNTREEIFY_THRESHOLD = 6; static final int MIN_TREEIFY_CAPACITY = 64;
复制代码
  • DEFAULT_INITIAL_CAPACITY 默认的初始化容量,即16;

  • DEFAULT_LOAD_FACTOR 默认的负载因子

  • TREEIFY_THRESHOLD 当一个槽(或叫bin、buket)的链表长度到达改阈值时,是将链表转换为红黑树的一个必要条件,注意只是一个必要条件,并不是充分条件,后面马上就会说明原因。

  • UNTREEIFY_THRESHOLD 当一个槽数据退化到该阈值时,红黑树将退化成链表;

  • MIN_TREEIFY_CAPACITY 当容量小于该值时,即使链表长度到达TREEIFY_THRESHOLD 也不会转换红黑树,而是通过resize()的方式进行扩容。所以别再说当链表长度大于8时就会转换红黑树了,这个条件不具备的情况下是不会转换的,具体代码在treeifyBin()方法中,下面也会讲到。

这里说一个反常识,可能是因为八股文背得多了,大家对HashMap链表转红黑树慢慢的认为是一个很容易发生的情况,但是从源码中我们其实可以看到官方的一些理论数据如下,可见正常情况下一个HashMap中出现红黑树的可能性是非常低的。

 * Ideally, under random hashCodes, the frequency of * nodes in bins follows a Poisson distribution * (http://en.wikipedia.org/wiki/Poisson_distribution) with a * parameter of about 0.5 on average for the default resizing * threshold of 0.75, although with a large variance because of * resizing granularity. Ignoring variance, the expected * occurrences of list size k are (exp(-0.5) * pow(0.5, k) / * factorial(k)). The first values are: * * 0:    0.60653066 * 1:    0.30326533 * 2:    0.07581633 * 3:    0.01263606 * 4:    0.00157952 * 5:    0.00015795 * 6:    0.00001316 * 7:    0.00000094 * 8:    0.00000006 * more: less than 1 in ten million
复制代码

HashMap的构造方法

     public HashMap(int initialCapacity, float loadFactor) {         if (initialCapacity < 0)             throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);         if (initialCapacity > MAXIMUM_CAPACITY)             initialCapacity = MAXIMUM_CAPACITY;         if (loadFactor <= 0 || Float.isNaN(loadFactor))             throw new IllegalArgumentException("Illegal load factor: " + loadFactor);         this.loadFactor = loadFactor;         this.threshold = tableSizeFor(initialCapacity); //这个方法也很有意思,后面会讲     }     public HashMap(int initialCapacity) {         this(initialCapacity, DEFAULT_LOAD_FACTOR);     } ​     public HashMap() {         this.loadFactor = DEFAULT_LOAD_FACTOR;      } ​     public HashMap(Map<? extends K, ? extends V> m) {         this.loadFactor = DEFAULT_LOAD_FACTOR;         putMapEntries(m, false);     }
复制代码

这里值得注意的是,在构造方法中,除最后一个构造方法外,其他构造方法中并没有真的去初始化我们熟悉的链桶结构。

那什么时候初始化的呢?其实是在put()数据时才会触发真正的初始化,这里我理解为一种延迟初始化的策略。

还有一个常见的说法是在能够明确集合大概容量的情况下推荐使用HashMap(int initialCapacity)的方式进行构造,原因主要是这样减少了因为容量增长导致的resize()操作。

关于Hash()

HashMap的hash方法比较有意思,也能引一些代码细节上的思考。

    static final int hash(Object key) {         int h;         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);     }
复制代码

为什么需要(h = key.hashCode()) ^ (h >>> 16) 即 将key的hashcode的前16位与后16位进行异或操作。

在说原因之前我们看下这个hash值的使用场景一般是在计算一个key/value应该落在table[]的哪个槽里,会对key进行hash(key)操作,得到一个hash值,而槽的index的计算方式一般是(n - 1) & hash这里n 一般为2的整数次方,所以n-1的二级制一般是都是1,如8-1的二进制是111,16-1=binary:1111,所以(n-1)&hash肯定是小于等于n-1的,所以当hash满足随机性,其计算出来的index也具备随机性。那为何不直接使用hashCode呢?为啥还要多此一举搞一个异或呢?

这是因为通常情况下n的值不会特别大,这种计算方式往往只能与hash的后几位进行运算,这样就可能出现一些高位不同,地位相同的hash值计算出同一个结果,导致冲突概率增加。

所以回过来看一下(h = key.hashCode()) ^ (h >>> 16) 就能够理解一些了,将高位16位与低16位进行异或,让高位的随机性影响到地位,从而达到让冲突的概率更低的效果。

是不是很巧妙。

//先到这,待更新


 

全部评论

相关推荐

08-25 14:48
已编辑
门头沟学院 人工智能
搜索部&nbsp;首先说下timeline8.18,投递8.19,约一面8.21,晚上一面call约二面8.22,上午二面下午oc周末等待(8.23,8.24)8.25,offer一年前,我还是懵懵懂懂,高考完的暑假,只会提前学学高数,未来的画像是什么?我或许无法预测。开学后,自学Python,接单,无数个客户的ddl,偷偷摸摸一个人找自习的地方,这一步步竟然为后来的我,搭建工程能力的基础。大一上,我也要感谢我的第一位老板,让我接触到了实习,师兄带着我一步步入门,看他们写的飞书文档。大一下,导师带我参与企业项目,这让我渐渐发现,应该去实践,增长见识,而非局限当下,盯着自己的小新pro。不久后,第一波投递开始,结果当然是约面极少。盯着简历上的文字和ssob,我开始思考,确实很多可以去提升。带着些许不甘心,继续沉淀,慢慢的约面也越来越多,有的时候两天7场,准备完就接着下一个日程。这一次,也许是刚好到位吧,比较match,面试答的流利,关关难关关过,成为度孝子展望未来,依然是重重挑战,果然只有收到offer的那一刻是开心的。愿在百度星海拆解的每一段代码,都能成为丈量宇宙的诗行;此志终赴星河,而今迈步重铸天阶。屏幕前的你们,在无数个向星海奔赴的日夜,一定一定,会在未来化作群星回响的征程——请永远相信此刻埋首耕耘的自己!!!
ResourceUt...:28届之光
百度求职进展汇总
点赞 评论 收藏
分享
驼瑞驰_招募评论官版...:点击就挂,露头就秒
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务