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对象存储key和value(hash/next)
get方法同理。
HashMap的存储结构:初始化时候是长度为16的数组。数组存放了entry类对象,entry类的
HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干。
Entry对象的属性:
static
class
Entry
implements
Map.Entry
{
final
Kkey; // key key
不能变。
Vvalue; //
值
Entrynext; // next
指针
final
int
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函数呢(减少冲突)?
我们通过利用Key的HashCode值来做某种运算。如何进行位运算呢?
有如下的公式(Length是HashMap的长度):
index= HashCode(Key) & (Length - 1)
下面我们以值为“book”的Key来演示整个过程:
1.计算book的hashcode,结果为十进制的3029737,二进制的101110001110101110 1001。
2.假定HashMap长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111。
3.把以上两个结果做与运算,101110001110101110 1001 & 1111 =1001,十进制是9,所以 index=9。
可以说,Hash算法最终得到的index结果,完全取决于Key的Hashcode值的最后几位。
例子:
假设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的实现原理的主要内容,如果未能解决你的问题,请参考以下文章