- JDK1.7HashMap采用的是数据结构是:数组 + 链表
- JDK1.8则采用的是:数组 + 链表 + 红黑树
在Java 8 之前,HashMap和其他基于map的类都是通过链地址法解决冲突,它们使用单向链表来存储相同索引值的元素。在最坏的情况下,这种方式会将HashMap的get方法的性能从O(1)降低到O(n)。为了解决在频繁冲突时hashmap性能降低的问题,Java 8中使用平衡树来替代链表存储冲突的元素。这意味着我们可以将最坏情况下的性能从O(n)提高到O(logn)。
在Java 8中使用常量TREEIFY_THRESHOLD来控制是否切换到平衡树来存储。目前,这个常量值是8,这意味着当有超过8个元素的索引一样时,HashMap会使用树来存储它们。
这一动态的特性使得HashMap一开始使用链表,并在冲突的元素数量超过指定值时用平衡二叉树替换链表。不过这一特性在所有基于hash table的类中并没有,例如Hashtable和WeakHashMap。
目前,只有ConcurrentHashMap,LinkedHashMap和HashMap会在频繁冲突的情况下使用平衡树。
什么时候会产生冲突
HashMap中调用hashCode()方法来计算hashCode。
由于在Java中两个不同的对象可能有一样的hashCode,所以不同的键可能有一样hashCode,从而导致冲突的产生。
如何解决hash冲突
Hash算法解决冲突的方法一般有以下几种常用的解决方法:
- 开放定址法:一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入
- 再哈希:又叫双哈希法,有多个不同的Hash函数,当发生冲突时,使用第二个,第三个,….,等哈希函数计算地址,直到无冲突。虽然不易发生聚集,但是增加了计算时间。
- 链地址法:链地址法的基本思想是:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表连接起来。
- 建立公共溢出区:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。
HashMap在处理冲突时使用链表存储相同索引的元素。
从Java 8开始,HashMap,ConcurrentHashMap和LinkedHashMap在处理频繁冲突时将使用平衡树来代替链表,当同一hash桶中的元素数量超过特定的值便会由链表切换到平衡树,这会将get()方法的性能从O(n)提高到O(logn)。
当从链表切换到平衡树时,HashMap迭代的顺序将会改变。不过这并不会造成什么问题,因为HashMap并没有对迭代的顺序提供任何保证。
从Java 1中就存在的Hashtable类为了保证迭代顺序不变,即便在频繁冲突的情况下也不会使用平衡树。这一决定是为了不破坏某些较老的需要依赖于Hashtable迭代顺序的Java应用。
除了Hashtable之外,WeakHashMap和IdentityHashMap也不会在频繁冲突的情况下使用平衡树。
使用HashMap之所以会产生冲突是因为使用了键对象的hashCode()方法,而equals()和hashCode()方法不保证不同对象的hashCode是不同的。需要记住的是,相同对象的hashCode一定是相同的,但相同的hashCode不一定是相同的对象。
在HashTable和HashMap中,冲突的产生是由于不同对象的hashCode()方法返回了一样的值。
从JDK 8开始,HashMap,LinkedHashMap和ConcurrentHashMap为了提升性能,在频繁冲突的时候使用平衡树来替代链表。因为HashSet内部使用了HashMap,LinkedHashSet内部使用了LinkedHashMap,所以他们的性能也会得到提升。
HashMap的快速高效,使其使用非常广泛。其原理是,调用hashCode() 和equals() 方法,并对hashcode进行一定的哈希运算得到相应value的位置信息,将其分到不同的桶里。桶的数量一般会比所承载的实际键值对多。当通过key进行查找的时候,往往能够在常数时间内找到该value。但是,当某种针对key的hashcode的哈希运算得到的位置信息重复了之后,就发生了哈希碰撞。这会对HashMap的性能产生灾难性的影响。在Java 8 之前,如果发生碰撞往往是将该value直接链接到该位置的其他所有value的末尾,即相互碰撞的所有value形成一个链表。因此,在最坏情况下,HashMap的查找时间复杂度将退化到O(n)。在Java 8 中,该碰撞后的处理进行了改进。当一个位置所在的冲突过多时,存储的value将形成一个排序二叉树,排序依据为key的hashcode。则在最坏情况下,HashMap的查找时间复杂度将从O(1) 退化到O(logn)
JDK1.7和JDK1.8 HashMap的区别
- 最重要的一点是底层结构不一样,1.7是数组+链表,1.8则是数组+链表+红黑树结构;
- jdk1.7中当哈希表为空时,会先调用inflateTable()初始化一个数组;而1.8则是直接调用resize()扩容;
- 插入键值对的put方法的区别,1.8中会将节点插入到链表尾部,而1.7中是采用头插;
- jdk1.7中的hash函数对哈希值的计算直接使用key的hashCode值,而1.8中则是采用key的hashCode异或上key的hashCode进行无符号右移16位的结果,避免了只靠低位数据来计算哈希时导致的冲突,计算结果由高低位结合决定,使元素分布更均匀;
- 扩容时1.8会保持原链表的顺序,而1.7会颠倒链表的顺序;而且1.8是在元素插入后检测是否需要扩容,1.7则是在元素插入前;
- jdk1.8是扩容时通过hash&cap==0将链表分散,无需改变hash值,而1.7是通过更新hashSeed来修改hash值达到分散的目的;
- 扩容策略:1.7中是只要不小于阈值就直接扩容2倍;而1.8的扩容策略会更优化,当数组容量未达到64时,以2倍进行扩容,超过64之后若桶中元素个数不小于7就将链表转换为红黑树,但如果红黑树中的元素个数小于6就会还原为链表,当红黑树中元素不小于32的时候才会再次扩容。
为什么Java后端面试失败
以下是一些可能导致Java面试失败的常见原因:
1. 缺乏知识和经验:Java是一门广泛使用的编程语言,并且有许多库、框架和工具可供使用。如果你在关键领域缺乏实际经验或缺乏对特定库或框架的详细知识,那么很容易在面试过程中被问及相关问题无法回答。
2. 不良的交流:您在Java面试中的口头表达和非语言表达都至关重要。如果您的回答太简洁或太啰嗦,可能会给面试官留下不好的印象。另外,如果您的态度不积极或者缺乏自信,则会影响与面试官的互动,甚至导致失败。
3. 没有准备好技术问题:Java开发人员应该具备基本的计算机科学知识以及针对该语言的高级概念。如果您没有花时间研究这些主题并做好充足的准备,那么当面试官提问这些问题时就有可能会感到困惑或无法回答。
4. 没有准备与 Java 相关的编程问题:Java面试通常会包括编程方面的问题,特别是算法和数据结构方面。如果您没有仔细研究Java相关的算法和数据结构,则您在面试期间可能会遇到类似的问题,并且很
面试前需要掌握那些技能才能够实现涨薪?
要想实现涨薪,程序员需要掌握以下技能:
1. 编程语言:熟练掌握一门编程语言,并了解其相关的开发工具和框架。
2. 数据结构和算法:良好的数据结构和算法基础,可以写出高效、可扩展和易于维护的代码。
3. 项目管理:学会如何规划、设计、开发和部署一个项目。这可以让你在团队中发挥更大的作用并促进项目的成功。
4. 技术栈扩展:跟上技术变革,不断学习新的技术和工具。掌握最新的技术,有利于你扩展自己的技能,从而晋升到更高级别的职位。
5. 解决问题能力:面对简单甚至困难的技术难题,能够快速分析问题所在并提出解决方案,这是越来越受雇主所看重的技能。
6. 沟通协调能力:良好的沟通和交流能力,能够与团队其他成员合作开展工作,并向客户和领导传达技术信息。
综上所述,要想实现涨薪,程序员需要综合掌握各种技能,不断提高自己的技术水平和实际经验。
我最近整理了一些小伙伴们发给我的面试题以及我的一些最新的面试等学习资料,有需要的小伙伴可以找我领取下。或者点击 → 《2023最新Java后端全套VIP面试学习资源》直接获取以下Java后端架构VIP进阶学习面试资料。
资料里面包含了:Java基础、MySQL、jvm、分布式、性能优化、spring 、spring boot、spring cloud、 MyBatis、Netty源码分析、算法、乙级高并发、Redis、dubbo、Tomcat、集合框架、锁、MQ、百万简历模板等等学习视频资料。
资料如图展示:(知识其中一部分)
同时也欢迎大家关注公众号【Java烂猪皮】,回复【666】,获取最新Java后端架构VIP学习资料以及视频学习教程,然后一起学习,一文在手,面试我有。
公众号【Java烂猪皮】里面每天都会分享很多独家的干货内容,比如:Java后端学习路线,分享实战项目,源码分析,百万级系统设计,系统上线的一些坑,MQ专题,真实面试题,每天都会回答大家提出的问题。
每一个专栏都是大家非常关心,和非常有价值的话题,我相信在专栏中你会学到很多东西,一起共勉。