(1) HashMap在jdk1.7与1.8两个版本中有什么区别?

jdk7 数组+单链表 jdk8 数组+(单链表+红黑树)
jdk7 链表头插 jdk8 链表尾插

头插: resize后transfer数据时不需要遍历链表到尾部再插入

头插: 最近put的可能等下就被get,头插遍历到链表头就匹配到了

头插: resize后链表可能倒序; 并发resize可能产生循环链

jdk7 先扩容再put jdk8 先put再扩容 (why?有什么区别吗?)

  1. //其实就是当这个Map中实际插入的键值对的值的大小如果大于这个默认的阈值的时候(初始是16*0.75=12)的时候才会触发扩容,
  2. //这个是在JDK1.8中的先插入后扩容
  3. if (++size > threshold)
  4. resize();
  5. 但是在JDK1.7中的话,是先进行扩容后进行插入的,就是当你发现你插入的桶是不是为空,如果不为空说明存在值就发生了hash冲突,那么就必须得扩容,但是如果不发生Hash冲突的话,说明当前桶是空的(后面并没有挂有链表),那就等到下一次发生Hash冲突的时候在进行扩容,但是当如果以后都没有发生hash冲突产生,那么就不会进行扩容了,减少了一次无用扩容,也减少了内存的使用
  1. jdk7 计算hash运算多 jdk8 计算hash运算少(http://www.jasongj.com/java/concurrenthashmap/#寻址方式-1)
  2. jdk7 受rehash影响 jdk8 调整后是(原位置)or(原位置+旧容量)
  3. 参考文章:https://blog.csdn.net/qq_36520235/article/details/82417949

    (2) HashMap的工作原理是怎样的?

    https://blog.csdn.net/liou825/article/details/18375741
    总结:
    1. HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。
    2. 当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。
    3. 当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用LinkedList来解决碰撞问题,当发生碰撞了,对象将会储存在LinkedList的下一个节点中。
    4. HashMap在每个LinkedList节点中储存键值对对象。

    当两个不同的键对象的hashcode相同时会发生什么?

    1. 它们会储存在同一个bucket位置的LinkedList中。键对象的equals()方法用来找到键值对。
    (3) HashMap是如何确定键值对的位置?如何解决Hash冲突?
    (4) HashMap存值过程中什么时候进行数组扩容?
    (5) HashMap扩容为什么每次都是2的次幂?
    (6) HahsMap底层为什么要使用异或运算符?
    为什么在JDK1.8中进行对HashMap优化的时候,把链表转化为红黑树的阈值是8,而不是7或者不是20呢?
  • 如果选择6和8(如果链表小于等于6树还原转为链表,大于等于8转为树),中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。
  • 还有一点重要的就是由于treenodes的大小大约是常规节点的两倍,因此我们仅在容器包含足够的节点以保证使用时才使用它们,当它们变得太小(由于移除或调整大小)时,它们会被转换回普通的node节点,容器中节点分布在hash桶中的频率遵循泊松分布,桶的长度超过8的概率非常非常小。所以作者应该是根据概率统计而选择了8作为阀值

(7) HashMap中的加载因子为什么是0.75,如果调整为1呢?
(8) HashMap的线程安全问题发生在哪个阶段?
(9) HashMap和ConcurrentHashMap有什么 区别?
(10) ConcurrentHashMap是如何实现线程 安全的?
说一下hashmap的put方法:
image.png