1. HashMap在jdk1.7跟1.8有什么区别?

1.7 数组加链表的结构
1.8 数组加链表或者红黑树,什么时候是红黑树,当数组长度扩容到64之后,并且相同的
节点下容量>8时(同一个节点下可能会出现>8的情况,TODO//最大为多少)
image.png会出现同一个节点大于8的情况

2. 1.8以后为什么要使用红黑树

因为在1.8之前都是链表结构,如果链表过长的话影响hashMap的性能

3. 为什么不一开始就是使用红黑树,而是先是链表再树化

因为数据量比较小的时候,短的链表结构其实刚开始是比树效率高的
而且红黑树占用的内存比较多,链表数据结构是NODE,而树是TREENODE
TREENODE比NODE的成员变量多很多

4.为什么是超过8,而不是超过其他的?阈值为什么是8?

image.png
image.png
因为选择8的话树化的几率为亿分之8,选择8就是为了树化的几率足够小
TreeNode占用空间也比普通的=Node的大,如非必要,尽量还是使用链表

5. 索引是如何计算的?hashCode都有了,为何还要提供hash()方法?

  1. 先进行对象的HashCode,再进行调用HashMap的Hash()方法,最后是&(capacity-1)计算得到索引

jdk 1.7:(移位或者异或操作)
image.png
jdk1.8:(移位或者异或操作)
image.png
因为一次hash值计算并不能使得数据分布均匀, 防止超长的链表产生

6. 数组容量为什么是2的n次幂?

  1. 计算索引的时候,如果是2的n次幂的话可以使用位与运算代替取模,效率更高了
  2. 扩容的时候使用hash&oldCap==0的元素留在原来的位置,否则新的位置= 旧的位置+oldCap;

image.png
image.png(所在位置+原始容量)
使用2的n次幂是为了追求效率:
image.png

7. 红黑树什么时候转换成链表的

  1. 扩容时数量小于等于6时
  2. remove之前红黑树的左孩子,右孩子,或者左孙子不存在的情况下,会转换成链表

8. 介绍一下put方法的流程,1.7与1.8有何不同?

  1. HashMap懒得创建数组的,首次使用才创建数组
  2. 计算索引(计算桶下标)
  3. 如果桶下标还没人占用,创建Node占位返回
  4. 如果桶下标已经有人占用
    1. 已经是TreeNode走红黑树的添加或更新逻辑
    2. 是普通Node,走链表的添加或者跟新逻辑,如果说链表长度超过树化阈值走树化逻辑
  5. 返回前检查容量是否超过阈值一旦超过进行扩容
  6. 1.7 是大于阈值且没有空位时才扩容,而1.8是大于阈值就扩容

image.png

  1. 1.8在进行扩容计算Node索引时,会优化(新的位置会按位与)

9. 加载因子默认为什么是0.75F

  1. 在空间占用与查询时间之间取得较好的权衡
  2. 大于这个值,空间节省了,但链表就会比较长影响性能
  3. 小于这个值,冲突减少了,但扩容就会更频繁,空间占用多

10. 多线程会出现什么问题?

  1. 扩容死链(1.7)
  2. 数据错乱(1.7, 1.8)

image.png

11.key是否为null,作为key的对象有什么要求?

  1. hashMap的key可以为null,但Map的其他实现则不然
  2. 作为key的对象,必须实现hashCode和equals,并且key的内容不能修改(不可变)、

3.null的时候放的位置:
image.png

12. String对象的hashCode()如何设计的,为啥每次乘的31

image.png

13. HashTable

初始容量是11,每次扩容的话是2n+1,加载因子也是0.75
HashCode值不需要二次hash,因为它的容量不是2的n次幂,扩散性比较好
线程安全的,给整个集合上锁,一次只能由一个线程操作(并发很低)
image.png

14. ConcurrentHashMap

image.png