HashMap的理解(底层实现,扩容机制,经典提问)

HashMap的底层实现

JDK1.7 由数组+链表 ,在JDK1.8由数组+链表+红黑树
在这里插入图片描述
将entry<key,value>的键值对存储在table[]中,当key值相同时,用链表的形式存储。再JDK1.8里,当链表长度超过8时,会转化为红黑树存储,便于查找。

  • 为什么JDK1.8要使用红黑树?为什么不一开始就使用红黑树?
    链表中节点数到8的概率很低,此时用链表的查询效率已经很低了(线性),使用红黑树可以提高查询效率(Olgn)。树节点占用的内存是不同节点的两倍,在节点太多的时候才会因为效率使用红黑树。

HashMap的扩容

  • 如何扩容?扩容成多少?HashMap扩容的时机?
    HashMap默认的初始大小是16,之后每次扩容的大小是之前的两倍(newsize = oldsize*2)。在JDK1.8,当HashMap的元素个数超过了数组大小x负载因子(LoadFactor,默认0.75)就会对数组进行扩容。而在JDK1.7,还会看当前数组位置是否为空,如果数组为空,元素个数大于阈值也不会扩容。

  • HashMap的长度为什么要是2的幂次方?
    便于计算。HashMap是将key的hash值%数组长度的结果作为数组下标存放键值对,在取余操作中,如果被除数是2的幂次方,等价其与除数-1做&运算。相当于hash%length = hash&(length-1),&计算更快。
    -扩容重新计算位置时,JDK1.7和1.8的区别?
    JDK1.7:计算Hash值,与数组长度确定下标
    JDK1.8:原位置+原位置数组长度(要么在原位置,要么在原位置的二次幂位置)

  • 为什么JDK1.8扩容的时候不用Hash运算?
    扩容的长度是2的倍数,&操作的时候-1,这样&操作影响结果的只有最高位。举个栗子:数据A的Hash值15(1111),扩容前数组长度8(1000),扩容后数组长度16 (10000),hash&(length-1)计算。
    原位置:1111&0111=0111 扩容后位置:1111&1111= 1111 = 1000(原数组长度)+0111(原位置),此时hash&扩容前长度=1111&1000 != 0。也就是说,JDK1.8中,确定扩容的位置只需要用hash&原数组大小判断结果是否为0即可(为0,位置不变;不为0,原位置+原数组长度)。非常的巧妙、高效,可以自行去验证。

  • 扩容产生的死锁
    比较复杂,参考博客
    参考博客链接.

HashMap的工作原理

HashMap基于hashing原理,我们使用put(key,value)存储对象到HashMap,使用get(key)来获取对象。当使用put()时,先对key调用hashCode()方法来找到位置存储entry<key,value>对象。如果两个对象hashcode相等,调用equal方法判断是否是同一个对象(当key是结构体的时候,hashcode相同,但不equals)。如果Hashcode相同,则会用链表的形式存储。

HashMap的提问

  • 如果两个key的hashcode相同,如何获取值对象?
    用get()获取对应的链表,遍历链表用equals找到值对象
  • HashMap中能put两个相同的Key吗?
    原理上来说是不能的,因为HashMap的key是不可重复的,后put的会将之前存在的覆盖掉
  • 如何存入相同key的不同值?即不覆盖掉之前的value,一个key对应多个value
    第一想法是用一个list来存value,让后加入的value值添加到之前添加的后面。
    1.如果key是String类型,已经重写了HashCode()和equals(),创建一个自己的HashMap类,重写put()
public class MyHashMap<K> extends HashMap<K,String> {

  /**
   * 使用HashMap中containsKey判断key是否已经存在
   * @param key
   * @param value
   * @return
   */
  @Override
  public String put(K key, String value) {
      String newV = value;
      if (containsKey(key)) {
          String oldV = get(key);
          newV = oldV + "---" + newV;
      }
      return super.put(key, newV);
  }
}

2.如果key是自定义类,除了要自定义HashMap,还要重写equals和HashCode方法。
参考博客

  • 为什么HashMap 的key可以为null?
    从源码分析:
    在这里插入图片描述
    在这里插入图片描述

在put()中会调用hash()去计算key的hash值,当key为null时返回0.

都看到这里了,喜欢就点个赞吧

猜你喜欢

转载自blog.csdn.net/glum_0111/article/details/107566801