写在开篇。绕不开的java集合类---Hashmap总结。

开篇

  在大学快3年了,当周围同学们都在春招找到好实习时,看到自己其实在编程方面还是太过于追求广而全,认为什么都了解一点一定是好的,而完全忽略了编程方面深度地挖掘,在大二上学期学习Java,自认为对java主流框架了解如何使用就是精通Java了,其实还是too young ,too naive!还是要提高自己的知识水平!如果当时有人能问我,Hashmap是什么,它的底层原理是什么,怎么实现的?可能当初被狠狠打脸后就会纠正自己的学习方法了吧!

Hashmap是什么?

  在我现有的知识中,Hashmap是java集合类中的一员,但没有实现collection接口(List,Set类),Hashmap实现了Map接口,是以key--value形式储存的,非线程安全的,散列存储的Map类。由于它是非线程安全的,所以可能会多个线程同时插入Hashmap时导致Entry链形成死链,造成错误。怎么避免?当然是使用线程安全的集合类,如Hashtable,ConcurrentHashmap。但根据我的有限的理解,使用线程安全的类也并不能完全实现线程安全,在使用过程中还是要避免竞态条件的发生。

它的底层原理是什么?

  说了那么多,那Hashmap是怎么实现hash算法来做到散列存放的呢?当然还是要打开神秘的源码来看一下啦。

  这段英文的大概意思应该是hashmap的初始Enety数组的大小是16(为什么是16,下面会说),负载因子是0.75,也就是说,当Hashmap中的size值达到最大容量*0.75(如16*0.75=12)时就会进行扩容操作,同时将里面存放的元素也会相应的再次hash存放在不同位置。注意,如果多个线程同时put,由于Hashmap并非线程安全,所以多个线程会同时进行扩容,将有可能导致死链的产生。

  那为什么是16呢,我们可以想象一下怎么可以实现对元素进行散列,我们知道在java世界中,所有类都有Object的专属方法--equals()和hashCode()。当两个对象equals相同时,hashcode也相同,hashcode相同时equals不一定相同,hashcode不同时对象一定不相同。所以,我们会想到用key中的hashcode对Hashmap的最大容量进行余数操作,即可实现散列存储。

  但是,Hashmap的实现者并不是普通玩家,他们想到了2的次方的数-1会得到一个低位掩码,用其来对key的hashcode进行与操作,同样能得到余数操作相同的值,但它是位运算,非常的高效,但把Hashmap的容量限定在了2的次方,所以,为什么是16呢,应该有答案了吧。

/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    return h & (length-1);
}

   但问题又来了,我们知道直接使用对象的hashcode进行散列运算其实还是会产生很多hash冲突,导致Hashmap的效率减小,那有没有一种方法能减少hash冲突呢 。答案当然是有的,其实上方代码中的int h并不是普通的key的hashcode,而是hashcode的高16位与低16位进行一次亦或操作生成的值,为什么是16与16?因为int的大小就是32位TAT。。。这样的话能把hashcode的高位也参与进来散列运算当中,不只是低位进行运算。。这样二次哈希后能有效减少hash冲突,就不用equals方法去对应的Entry链中辛辛苦苦的查到对应的元素了。

//Java 8中的散列值优化函数
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);    //key.hashCode()为哈希算法,返回初始哈希值
}

   最后,当hash冲突时,通常的做法有2种,拉链法与开放地址法,Hashmap是使用拉链法解决冲突问题的,当二次哈希还是产生冲突时,会将元素添加在Entry链表中。

  第一次写博客,难免有些困难,希望能坚持下去吧,我也不是大牛,如果有说错,求轻拍TAT。。。

猜你喜欢

转载自blog.csdn.net/qq_36642340/article/details/80369344