HashMap 源码面试

问:

    先讲讲 hashMap 的数据结构

    jdk8 中 hashMap 是 数组 + 链表 + 红黑树, 每个数据单元是一个 Node 结构。(jdk 1.7 是 entry 数组,数据单元是 Entry)

  Node 结构有 key 字段, 有 value 字段, 还有 next 字段,还有 hash 字段。

  next 字段,就是发生 hash 冲突的时候,当前桶位中的 node 于冲突 node 

问: 

    HashMap 默认长度是 多少

答:

    16

问:

    散列表 是初始化的时候创建的,还是什么时候创建的。

答:

    散列表是懒加载机制,只有第一次 put 数据的时候,才创建。

问:  

    默认的负载因子是多少, 有啥作用

    0.75.

    计算扩容阈值用的,    

问:

    链表转化为红黑树需要什么条件

答:

    链表长度超过 8 的时候 和 当前散列表长度已经达到 64.。

  否则,slot 内链表长度到8, 也不会转树,仅仅发生一次 resize, 散列表扩容。

问:

    Node 对象内部,有一个 hash 字段, 然后这个 hash 字段的值 是 key 对象的 hashCoded 返回值吗。

答:

    这个不是的。

    高低位异或后 增大寻址的随机性,减少 hash 冲突

    hashMap put  写数据的具体流程。

答:

    put 数据的时候,先根据 key # hashCode 经过高低位异或,再按 位于&(table.lenth - 1)得到槽位下表,

    根据这个槽内的情况,put 的方式也不同

    有四种情况,1、槽位为空 slot == null,2、槽位不为空,但是 node 还没有链表化; 3、slot 内的node 已经链化,4、已经是slot内是红黑树。

              1、槽位为空 slot == null;

      直接占用。根据 key 和 value 直接包装成 一个 node 对象放入 slot.

    2、槽位不为空,但是 node 还没有链表化

      先对比 node 的key  和 put 对象的 key 是否完全相等;

      如果相等,再equal 相等,把新的 key value 替换当前 slot 的key value.

      如果不相等,就是 hash 冲突,(jdk8)是是哦那个尾插法, 在原有的slot 里的数据对象使用 next 插入新的对象。

            3、slot 内的node 已经链化,

      先对比 链表上的节点,如果有一致的话,还是替换操作。

      如果一样的节点,则追加到链表的尾部。(在 jdk7, 中如果插到链表头部,则可能形成循环链表,尾插法则不会。)

      并检查是否达到阈值。

    4、已经是slot内是红黑树。

      TreeNode 继承了 node 结构,在 node 基础上加了个字段, 分别是指向父节点的 parent,  指向左子节点left,指向右子节点的right,

  还有表示节点的 red. 

      然后,红黑树的插入操作,首先是找到一个合适的插入点,即插入节点的父节点,然后这个红黑树满足二叉树所有的排序特性,

  找父节点的操作和二叉树(排序)树完全一致的,这个二分查找算法映射出来的结构,就一个倒立的二叉树,左节点小于当前节点,右节点

  大于当前节点,每次向下查找一层,就排除掉一般数据。

      查找也分情况。第一种情况是一直向下查询,直到左子树或者右子树为 null, 说明整个树中, node 的 key 与当前 put key 一致的

  treeNode. 此时,就是探测节点就是父节点所在,当前插入节点,插入到父节点的左子树或者是右子树。根据,这个插入节点的 hash 值和

  父节点 hash 值大小决定 左右,插入会打破平衡,还需要一个红黑树的平衡算法。

    (还有左旋,右旋)

      第二种,情况就是,根节点向下探测的过程种,发现这个 treeNode 的 key 与当前 put 的 key 完全一致,就直接替换这个相同的节点。

       (这里有点蒙混,替换了还要看是否满足二叉树查找树的特点。)

问:

    红黑树的几个原则。

答:

    1、构成树的节点,有一个颜色属性,红 和 黑。

    2、根节点,必须是黑色的。 

    3、从叶节点,到根节点的路径上,每条路劲黑色节点一定是一致的。 

    3、红色节点不能相连,就是红色节点子节点一定为黑色。

问:

    左旋 和 右旋 是是什么

问:

    为什么要引入,红黑树这个结构。

答:

    解决 hash 冲突导致的链化严重的问题。

    链表查询慢,通过红黑树,提高查询节点的速度,就是以空间换时间。

    本身散列表理想查询效率 为 O (1), 但是链化比较导致查询速度为 O(n)  ,引入红黑树,查询效率为 O(lgN)

问:

    为什么链化 导致查询速度低。

答:

   如果 查找目标在 链表尾部,要一个一个 next 去查找。

问:

    扩容机制,什么时候会触发这个扩容

答:

    扩容的规则

问:

    为什么采用位移运算, 而不乘以 2 

    这里考虑到性能运算,cpu 不支持乘法运算,所有乘法运算,在指令层面转化为 加法实现。 

  如果,用位运算 对 cpu 就简洁高效,效率比较高。

问:

    如何进行数据迁移

    分四种情况,第一种情况,slot 存储的是 null, 第二种情况;存储的是个 node, node 没有链化;第三章,slot 存储一个链化的node,

  第四种,储存一个红黑树的根节点 TreeNode 对象。

    第一种情况,slot 存储的是 null。不用说,

    第二种情况;存储的是个 node, node 没有链化, 直接迁移就好了,根据新表的 tableSize 计算出 新表的位置,然后存放过去就可以了。

    第三种情况:需要把当前 slot 保存的 链表,拆分成两个链表,分别是 高位链 和 低位链,所有的 node  hash字段转化为二进制后,低位

  都是相同的,低位就是老表的 tableSize - 1 转化出来的 二进制数有效位。

    table 数组 长度 16, 16 -1 就是 15, 然后 15 转化出来的二级制数就是 1111.  低四位,高位是第5位,01111 .。。。。(这个老哥不太行)

问:

    红黑树怎么扩容

答:

    红黑树节点对象,这个 TreeNode 结构啊, 依然保留了 next 字段, 红黑树结构内部还维护着一个链表,

  新增和删除节点,仍然要维护这个链表,这个链表方便 拆分这个红黑树用的。也就是根据这个 高位 和 低位, 拆为 高位链 和 低位链。

  高位链数据,要存放到 新表, 下标就是 老表的位置 + 老表的数组长度,然后计算出来的这个位置。

  低位链存放的位置和老表位置一样。

    如果 链表长度小于 8, 直接把这个 TreeNode 转化为普通的 node 链表, 然后放在扩容后的新表就可以了。

    如果拆分出来的链表,长度大于 8,还是需要把这个链表升级为红黑树。

     

原文地址:https://www.cnblogs.com/Jomini/p/13827225.html