(1)美团面试题:Hashmap的结构,1.7和1.8有哪些区别,史上最深入的分析「建议收藏」

(一)真实面试题之:Hashmap的结构,1.7和1.8有哪些区别不同点:(1)JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,那么他们为什么要这样做呢?因为JDK1.7是用单链表进行的纵向延伸,当采用头插法就是能够提高插入的效率,但是也会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。(2)扩…

大家好,又见面了,我是你们的朋友全栈君。

(一) 真实面试题之:Hashmap的结构,1.7和1.8有哪些区别

不同点:

(1)JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,那么他们为什么要这样做呢?因为JDK1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。

(2)扩容后数据存储位置的计算方式也不一样:1. 在JDK1.7的时候是直接用hash值和需要扩容的二进制数进行&(这里就是为什么扩容的时候为啥一定必须是2的多少次幂的原因所在,因为如果只有2的n次幂的情况时最后一位二进制数才一定是1,这样能最大程度减少hash碰撞)(hash值 & length-1)

(3)在JDK1.7的时候是先扩容后插入的,这样就会导致无论这一次插入是不是发生hash冲突都需要进行扩容,如果这次插入的并没有发生Hash冲突的话,那么就会造成一次无效扩容,但是在1.8的时候是先插入再扩容的,优点其实是因为为了减少这一次无效的扩容,原因就是如果这次插入没有发生Hash冲突的话,那么其实就不会造成扩容,但是在1.7的时候就会急造成扩容

(4)而在JDK1.8的时候直接用了JDK1.7的时候计算的规律,也就是扩容前的原始位置+扩容的大小值=JDK1.8的计算方式,而不再是JDK1.7的那种异或的方法。但是这种方式就相当于只需要判断Hash值的新增参与运算的位是0还是1就直接迅速计算出了扩容后的储存方式。

	![这里写图片描述](https://img-blog.csdn.net/20180905103627222?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTIwMjM1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
	在计算hash值的时候,JDK1.7用了9次扰动处理=4次位运算+5次异或,而JDK1.8只用了2次扰动处理=1次位运算+1次异或。

扩容流程对比图:
这里写图片描述
(3)JDK1.7的时候使用的是数组+ 单链表的数据结构。但是在JDK1.8及之后时,使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(logN)提高了效率)

这里写图片描述

这里在重新进行补充两个问题:(2019-09-03)

(1)为什么在JDK1.7的时候是先进行扩容后进行插入,而在JDK1.8的时候则是先插入后进行扩容的呢?

//其实就是当这个Map中实际插入的键值对的值的大小如果大于这个默认的阈值的时候(初始是16*0.75=12)的时候才会触发扩容,
//这个是在JDK1.8中的先插入后扩容
if (++size > threshold)
            resize();
  • 其实这个问题也是JDK8对HashMap中,主要是因为对链表转为红黑树进行的优化,因为你插入这个节点的时候有可能是普通链表节点,也有可能是红黑树节点,但是为什么1.8之后HashMap变为先插入后扩容的原因,我也有点不是很理解?欢迎来讨论这个问题?
  • 但是在JDK1.7中的话,是先进行扩容后进行插入的,就是当你发现你插入的桶是不是为空,如果不为空说明存在值就发生了hash冲突,那么就必须得扩容,但是如果不发生Hash冲突的话,说明当前桶是空的(后面并没有挂有链表),那就等到下一次发生Hash冲突的时候在进行扩容,但是当如果以后都没有发生hash冲突产生,那么就不会进行扩容了,减少了一次无用扩容,也减少了内存的使用
void addEntry(int hash, K key, V value, int bucketIndex) { 
   
		//这里当钱数组如果大于等于12(假如)阈值的话,并且当前的数组的Entry数组还不能为空的时候就扩容
      if ((size >= threshold) && (null != table[bucketIndex])) { 
   
       //扩容数组,比较耗时
          resize(2 * table.length);
          hash = (null != key) ? hash(key) : 0;
          bucketIndex = indexFor(hash, table.length);
      }

      createEntry(hash, key, value, bucketIndex);
  }

 void createEntry(int hash, K key, V value, int bucketIndex) { 
   
      Entry<K,V> e = table[bucketIndex];
    //把新加的放在原先在的前面,原先的是e,现在的是new,next指向e
      table[bucketIndex] = new Entry<>(hash, key, value, e);//假设现在是new
      size++;
  }

(2)为什么在JDK1.8中进行对HashMap优化的时候,把链表转化为红黑树的阈值是8,而不是7或者不是20呢(面试蘑菇街问过)?

  • 如果选择6和8(如果链表小于等于6树还原转为链表,大于等于8转为树),中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。
  • 还有一点重要的就是由于treenodes的大小大约是常规节点的两倍,因此我们仅在容器包含足够的节点以保证使用时才使用它们,当它们变得太小(由于移除或调整大小)时,它们会被转换回普通的node节点,容器中节点分布在hash桶中的频率遵循泊松分布,桶的长度超过8的概率非常非常小。所以作者应该是根据概率统计而选择了8作为阀值
	//Java中解释的原因
   * Because TreeNodes are about twice the size of regular nodes, we
     * use them only when bins contain enough nodes to warrant use
     * (see TREEIFY_THRESHOLD). And when they become too small (due to
     * removal or resizing) they are converted back to plain bins. In
     * usages with well-distributed user hashCodes, tree bins are
     * rarely used. Ideally, under random hashCodes, the frequency of
     * nodes in bins follows a Poisson distribution
     * (http://en.wikipedia.org/wiki/Poisson_distribution) with a
     * parameter of about 0.5 on average for the default resizing
     * threshold of 0.75, although with a large variance because of
     * resizing granularity. Ignoring variance, the expected
     * occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
     * factorial(k)). The first values are:
     *
     * 0:    0.60653066
     * 1:    0.30326533
     * 2:    0.07581633
     * 3:    0.01263606
     * 4:    0.00157952
     * 5:    0.00015795
     * 6:    0.00001316
     * 7:    0.00000094
     * 8:    0.00000006
     * more: less than 1 in ten million

(二)哈希表如何解决Hash冲突?

这里写图片描述

(三)为什么HashMap具备下述特点:键-值(key-value)都允许为空、线程不安全、不保证有序、存储位置随时间变化

这里写图片描述

(四)为什么 HashMap 中 String、Integer 这样的包装类适合作为 key 键

这里写图片描述

(五)HashMap 中的 key若 Object类型, 则需实现哪些方法?

这里写图片描述

参考文章;https://www.jianshu.com/p/8324a34577a0?utm_source=oschina-app

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/127024.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • pycharm连接mysql数据库代码_myeclipse连接数据库

    pycharm连接mysql数据库代码_myeclipse连接数据库PyCharm是一款常用的Python开发的软件,这里给大家介绍一下如何在PyCharm如何连接MySQL数据库。首先,我们需要找到页面中的DataBase窗口,一般在页面的右侧,没有显示的话,可以点击View->ToolWindows->DataBase。然后显示DataBase之后,我们点击DataBase。然后我们点击DataBase的这个+号。然后选择DataSource->MySQL。然后就显示到连接的页面,如果是第一次连接的时候,就需要点击下面的D

    2022年8月27日
    3
  • Java自定义类加载器「建议收藏」

    Java自定义类加载器「建议收藏」一.Java类加载器的分类引导类加载器(bootstrapclassloader):用于加载Java的核心库,JDK自带,C++代码实现的 扩展类加载器(extensionsclassloader):用于加载Java扩展库,JDK自带 系统类加载器(systemclassloader):用于加载classpath路径下的类,也就是我们编写的应用程序 自定义类加载器(customclassloader):用于加载自定义的类,这个是重点二.Java自定义类加载器的使用场景依赖冲..

    2025年9月19日
    5
  • idea激活码mac【在线破解激活】「建议收藏」

    idea激活码mac【在线破解激活】,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月17日
    64
  • python的赋值功能很强大_基础解系怎么赋值

    python的赋值功能很强大_基础解系怎么赋值前言增强型赋值语句是经常被使用到的,因为从各种学习渠道中,我们能够得知i+=1的效率往往要比i=i+1更高一些(这里以+=为例,实际上增强型赋值语句不仅限于此)。所以我们会乐此不

    2022年7月31日
    9
  • 时序数据库Machbase「建议收藏」

    时序数据库Machbase「建议收藏」Background最近偶然接触到Machbase,发现相关信息很少,于是自己做了一些简单的了解,这里记录下,方便有兴趣的童靴参考哈。1、官方介绍Machbase是韩国的一家公司开发的一款类似InfluxDB、DolphinDB、TDengine等时序数据库产品,不开源,不过单机fog版可以免费试用,具体可以参考官方网站:https://www.machbase.com/product/edge;但是国外的网站访问不便,这里给个下载的链接,想试用的可以下载:下载链接:https://pan.

    2022年10月4日
    2
  • Android 多线程之HandlerThread 完全详解

    转载请注明出处(万分感谢!):http://blog.csdn.net/javazejian/article/details/52334098出自【zejian的博客】关联文章:Android多线程之HandlerThread完全详解Android多线程之IntentService完全详解  之前对线程也写过几篇文章,不过倒是没有针对android,因为java

    2022年4月1日
    33

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号