副本第八关:HashMap的底层原理?HashMap多线程导致死循环问题?

146 阅读3分钟
  1. HashMap底层原理?

    • HashMap初始容量为16,负载因子为0.75,当hashMap容量为16*0.75 = 12时,会发生第一次扩容,扩容为2的n次方。

    • 负载因子默认为0.75的原因是,负载因子过高,例如为1,虽然减少了空间开销,提高了空间利用率,但是去查询元素的时候,会增加时间成本;负载因子过低,例如为0.5,虽然可以减少时间成本,但是空间利用率低,所以选择0.75完全是时间和空间成本上的一种折中的选择。

    • 扩容是2的n次幂的原因是:容量是2的n次幂,可以让添加的元素更均匀的分布在hashMap的数组上,减少hash碰撞;扩容时会rehash重新分配链表上的元素,rehash的取余操作,hash % length == hash & (length - 1)这个关系只有在length等于2的幂次方时才成立,位运算比%高效的多。

    • map.put(k, v)实现原理:①首先将k,v封装到Node对象当中(节点);②调用k的hashCode()方法计算出哈希值;③通过哈希函数/哈希算法,将hash值转化为数组下标,下标的位置上如果没有任何元素,就把Node添加到这个位置上,如果说下标对应的位置上有链表,此时,就会拿着k和链表上每个节点的k进行equal,如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的尾部,如果其中有一个equals返回了true,那么这个节点的value将被覆盖。

    • map.get(k)实现原理:①先调用k的hashCode()方法得到哈希值,并通过哈希算法将hash值转化为数组下标;②通过数组下标快速定位到某个位置上,如果这个位置上什么都没有,则返回null,如果这个位置上有单向链表,那么它就会拿着k和单向链表上每一个节点的k进行equals,如果所有equals方法都返回false,则get方法返回null,如果其中一个节点的k和参数k进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

    • 当hash表的单一链表长度超过8个的时候,链表结构就会转为红黑树结构,为什么要这样设计呢?好处就是避免在极端情况下链表变得很长很长,在查询的时候,效率会非常慢,因为链表查询,它需要遍历全部元素才行,时间复杂度O(n);而红黑树查询的访问性能近似于折半查找,时间复杂度为O(logn),这是因为红黑树是一种近似平衡的二叉查找树,其主要的优点就是“平衡”,即左右子树高度几乎一致,以此来防止树退化为链表,通过这种方式来保证查找的时间复杂度为log(n)。

  2. HashMap多线程导致死循环问题?

    • 主要原因是多线程扩容的时候,我们会rehash,jdk1.7的时候rehash是头插法,这个时候链表元素移动到新的位置的时候,它们的次序会反过来,这样会造成元素形成一个循环的链表,这个时候我们再去get数据,就会造成死循环问题,不过,jdk1.8解决了这个问题,改成了尾插法,但是还是不建议在多线程下使用hashMap,因为多线程的hashMap会存在数据丢失的问题。