HashMap解决hash冲突

背景:在Java编程语言中,最基本的数据结构就两种:一种是数组;一种是模拟指针(引用)。

  所有的数据结构都可以用这两种基本结构进行构造,HashMap也一样。

  当程序试图将多个key-value放入HashMap中时,HashMap采用一种所谓的“Hash算法”来决定每个元素的存储位置。

1. HashMap存储数据执行原理。

当程序执行map.put(String, Object)方法时,系统将调用String的hashCode()方法得到其hashCode值,每一个Java对象都有hashCode()方法,

都可以通过该方法获取它的hashCode值。


系统会根据该hashCode值来决定该元素的存储位置。


当系统决定存储HashMap中的key-value对时,仅仅只是根据key来计算每个Entry(key-value)的存储位置。我们完全可以把Map集合中的value当成key的附属,

当系统决定了key的存储位置之后,value随之保存在那里即可。


2. 什么是hash冲突?

如果存在相同的hashCode值,那么它们确定的索引位置就相同,这时判断它们的key是否相同,如果不相同,那么就产生了hash冲突。


3. 解决hash冲突。

散列表/哈希表要解决的一个问题就是散列值的冲突问题,通常有两种方法:链表法和开放地址法。

链表法,将相同hash值的对象组织成一个链表放在hash值对应的槽位

开放地址法,通过一个探测算法,当某一个槽位已经被占据的情况下,继续查找下一个可以使用槽位。

而java.util.HashMap采用的是链表法的方式,链表是单向链表。形成单链表的核心代码如下:

  1. void addEntry(int hash, K key, V value, int bucketIndex) {  
  2.     Entry e = table[bucketIndex];  
  3.     table[bucketIndex] = new Entry(hash, key, value, e);  
  4.     if (size++ >= threshold)  
  5.         resize(2 * table.length);  
  6. bsp;  
上面方法的代码很简单,但其中包含了一个设计:系统总是将新添加的Entry对象放入table数组的 bucketIndex索引处————若buchetIndex索引处已经有一个Entry对象,

那么新添加的Entry对象指向原有的Entry对象(产生一个Entry链);如果buchetIndex索引处没有Entry对象,也就是上面代码中变量e为null,也就是新放入的Entry对象指向null,

也就是没有产生Entry链。

HashMap里面没有出现hash冲突时,没有形成单链表时,hashMap查找元素很快,get()方法能够直接定位到元素。但是出现单链表后,单个buchet里存储的不是一个Entry,而是一个Entry链,系统只能必须按顺序遍历每个Entry,直到找到想搜索的Entry为止——如果恰好要搜索的Entry位于该Entry链的最末端(该Entry是最早放入该buchet中的),那么系统必须循环到最后才能找到该元素。

当创建HashMap时,有一个默认的负载因子(load factor),其默认值为0.75,这是时间和空间成本上一种折中:增大负载因子可以减少Hash表(也就是那个Entry数组)所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的操作(hashMap的get()方法和put()方法都要用到查询);减小负载因子会提高数据查询的性能,但会增加hash表所占用的内存空间。

你可能感兴趣的:(Java)