HashMap的原理,我懂了。(未完全版本)

HashMap的数据结构

  • 数组+链表+红黑树
  • 数据是以节点的方式来存储的,每个节点中包含:Key,Value,Next指针,
    链表是为了规避哈希冲突而存在的,因为哈希冲突理论上是不能避免的。
    红黑树是为了解决链表长度存在多的时候,解决效率问题而存在的, 节点大于8,

默认大小是16,负载因子是0.75

面试题

所有的问题都是对知识点的理解,如果有不懂的问题,需要反向反思自己是否理解这个知识点。

  1. put同一个key的时候,返回值是之前的值。(源码中是这样写的。)
  2. HashMap的数据结构是怎么样的?为什么会使用链表结构,为什么会使用数组结构,为什么1.8开始会使用红黑树
    a. 数组1.7存的就是Key-Value的Entry,1.8寸的时候Node
  3. 链表新增数据的时候,是放在头结点还是尾结点。?
    a. 1.7采用的链表的头插法 (多线程扩容的时候会导致循环卡死。)
    b. 1.8采用链表的尾插法
  4. get的逻辑和问题,get的时候先经过hash计算只能取到数组的位置。(先取到头结点)
  5. 为什么HashMap在初始化数组容量的时候,一定要是2的次方数呢?
  6. 如何解决Hash冲突导致的链表很长的问题?会导致get方法很慢!
  7. HashMap的数组为什么需要扩容?==》为了提高get的效率。
  8. HashMap扩容的步骤是什么呢?
  9. HashMap扩容会导致哪些问题呢? 链表翻转
  10. HashMap不是线程安全的,多线程扩容可能会让链表循环搬运。搬运的时候,对导致两个节点形成循环链表。
    a. 多线程扩容的时候,会导致这个问题
    b. 问题原因:链表插入使用的是头插法,头插法的特性。两个线程共同调用扩容的时候就会出现循环。所以1.8版本就把它改成了尾插法。
  11. HashMap什么时候会进行rehash()
  12. HashMap的key为null的时候,是存在index为0的位置上的。

相关代码摘要

/**
 * Returns a power of two size for the given target capacity.
 */
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
这个方法太牛了。获取最近位置的2的次方数。
只能感叹JDK作者对于位运算的使用已经达到了出神入化的境界了。

问题:为什么HashMap在初始化数组容量的时候,一定要是2的次方数呢?

newTab[e.hash&(newCap-1)]=e;
需要进行或操作,来计算数组下标位置方法的时候,这个算法的应用就倒着推出来的newCap必须要是2的次方。

问题:如何解决Hash冲突导致的链表很长的问题?会导致get方法很慢!
H ^= k.hashcode()
这个方法为了提高最终hashcode的散列性

参考链接:

  1. 安其拉的博客:一个HashMap跟面试官扯了半个小时
  2. 跟着Mic学架构:HashMap的原理
  3. 图灵-周瑜,视频讲解1.7的hashmap原理:推荐
posted @ 2022-03-28 15:45  dawa大娃bigbaby  阅读(61)  评论(0编辑  收藏  举报