Hashmap的实现原理

Posted 一起写程序

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hashmap的实现原理相关的知识,希望对你有一定的参考价值。


 

问题1)hashmap的实现原理?(Hashmap的数据结构)

HashMap的实现原理是hash函数(hashing/散列),通过put(key,value)get(key,value)插入值和取出值。

取出值和放入值的过程就叫做散列法(哈希函数)

index == HashCode(key) / (length-1),数组中存储entry对象,entry对象存储keyvaluehash/next

get方法同理。

 

HashMap的存储结构:初始化时候是长度为16的数组。数组存放了entry类对象,entry类的

HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干。

 

Entry对象的属性:

staticclass Entryimplements Map.Entry

{

        final Kkey;     //  key key不能变。

        Vvalue;       //

        Entrynext;       // next 指针

        finalint hash;    // 计算出来的hash 值 (hashCode值)

        ...//More code goes here

}  

 

问题2)当put或get的hashCode相同时候?

Put方法中hashCode值相同

HashMap使用数组和链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。

这种方法是最简单的,也正是HashMap的处理方法。

Get方法中hashCode值相同

根据hashCode找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象

 

问题3)如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?存储空间不足时候?

默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

 

问题4)重新调整HashMap大小存在什么问题?

HashMap非线程安全。

当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。

问题5)为什么String, Interger这样的wrapper类适合作为键? 

String, Interger这样的wrapper类作为HashMap的键是再适合不过了,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个field声明成final就能保证hashCode是不变的,那么请这么做吧。因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能

1.  我们可以使用自定义的对象作为键吗? 这是前一个问题的延伸。当然你可能使用任何对象作为键,只要它遵守了equals()和hashCode()方法的定义规则,并且当对象插入到Map中之后将不会再改变了。如果这个自定义对象时不可变的,那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了。

问题6)我们可以使用CocurrentHashMap来代替Hashtable吗?

这是另外一个很热门的面试题,因为ConcurrentHashMap越来越多人用了。我们知道Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性。看看这篇博客查看Hashtable和ConcurrentHashMap的区别。

 

问题7)hashmap的初始化长度是多少?为什么这样规定?为什么每次扩展的空间必须是2的幂次?

1,初始化的长度是16

2,为了减少冲突,

问题8)如何实现一个尽量均匀分布的Hash函数呢(减少冲突)?

我们通过利用KeyHashCode值来做某种运算。如何进行位运算呢?

有如下的公式(LengthHashMap的长度):

index=  HashCodeKeyLength - 1 

下面我们以值为“book”Key来演示整个过程:

1.计算bookhashcode,结果为十进制的3029737,二进制的101110001110101110 1001

2.假定HashMap长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111

3.把以上两个结果做与运算,101110001110101110 1001 & 1111 =1001,十进制是9,所以 index=9

可以说,Hash算法最终得到的index结果,完全取决于KeyHashcode值的最后几位。

例子:

假设HashMap的长度是10,重复刚才的运算步骤:

                           

单独看这个结果,表面上并没有问题。我们再来尝试一个新的HashCode  101110001110101110 1011 

让我们再换一个HashCode 101110001110101110 1111 试试 


是的,虽然HashCode的倒数第二第三位从0变成了1,但是运算的结果都是1001。也就是说,当HashMap长度为10的时候,有些index结果的出现几率会更大,而有些index结果永远不会出现(比如0111)!

这样,显然不符合Hash算法均匀分布的原则。

反观长度16或者其他2的幂,Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。

问题8)为什么hashmap在高并发情况下会死锁?

问题9)在Java8中对hashmap做了那些优化?


以上是关于Hashmap的实现原理的主要内容,如果未能解决你的问题,请参考以下文章

从代码层读懂HashMap的实现原理

从代码层读懂HashMap的实现原理

从代码层读懂 Java HashMap 的实现原理

HashMap实现原理和源码详细分析

HashMap实现原理和源码详细分析

Java HashMap实现原理 源码剖析