JDK 1.7和JDK 1.8中的HashMap存在显著的区别,并且各自存在一些问题。以下是对两者的详细对比及问题分析:
一、区别
-
底层数据结构:
- JDK 1.7:HashMap的底层结构是由数组(也被称为“位桶”)和链表构成。当hash冲突时,不同的key映射到数组的同一位置,则形成链表。
- JDK 1.8:HashMap的底层结构是数组+链表/红黑树。当链表长度超过一定阈值(默认为8)时,链表会转换为红黑树,以提高查询效率。
-
初始化与扩容:
- JDK 1.7:当哈希表为空时,会先调用
inflateTable()
初始化一个数组;扩容时是先扩容后插入新值,而且扩容时可能会改变链表中元素的顺序。 - JDK 1.8:直接调用
resize()
进行扩容;扩容时是先插值再扩容,并且在扩容时会保持链表元素原本的顺序。
- JDK 1.7:当哈希表为空时,会先调用
-
插入数据方式:
- JDK 1.7:采用头插法插入键值对,由于使用单链表进行的纵向延伸,采用头插法时容易出现逆序且环形链表死循环问题。
- JDK 1.8:将节点插入到链表尾部,由于加入了红黑树并使用尾插法,能够避免出现逆序且链表死循环的问题。
-
Hash值计算:
- JDK 1.7:Hash函数对哈希值的计算直接使用key的
hashCode
值。 - JDK 1.8:采用key的
hashCode
异或上key的hashCode
进行无符号右移16位的结果,避免了只靠低位数据来计算哈希时导致的冲突,计算结果由高低位结合决定,使元素分布更均匀。
- JDK 1.7:Hash函数对哈希值的计算直接使用key的
-
扩容策略:
- JDK 1.7:当元素个数不小于阈值(即容量*负载因子)时,直接扩容2倍。
- JDK 1.8:当数组容量未达到64时,以2倍进行扩容;超过64之后若桶中元素个数大于6就将链表转换为红黑树,但如果红黑树中的元素个数小于6就会还原为链表;当红黑树中元素不小于32的时候才会再次扩容。
-
对null的处理:
- JDK 1.7:null是一个特殊的值,单独处理。
- JDK 1.8:null的hash值计算结果为0,其他地方和普通的key没区别。
-
Hash值的可变性:
- JDK 1.7:hash是可变的,因为存在rehash操作。
- JDK 1.8:hash是final修饰的,即hash值一旦确定就不会再重新计算。
二、各自的问题
-
JDK 1.7 HashMap的问题:
- 死循环和数据丢失:由于JDK 1.7采用头插法插入键值对,当进行扩容时,可能会出现逆序的链表,进而可能导致环形链表和死循环的问题。此外,这种插入方式在扩容时也可能导致数据丢失。
- Hash算法的复杂性:JDK 1.7的Hash算法相对复杂,包括多次位运算和异或运算,这可能影响HashMap的性能。
-
JDK 1.8 HashMap的问题:
- 红黑树的转换开销:虽然JDK 1.8引入了红黑树来优化查询性能,但当链表转换为红黑树或红黑树还原为链表时,需要额外的开销。
- 线程不安全:与JDK 1.7一样,JDK 1.8的HashMap也是线程不安全的。在多线程环境下使用时,可能会出现数据不一致的问题。因此,在高并发场景下,需要考虑使用线程安全的替代方案,如
ConcurrentHashMap
。
1.7和1.8的线程不安全的问题可以查看这篇文章:大厂面试真题-具体说说jdk1.7和1.8的hashmap的线程不安全都有什么问题-CSDN博客
综上所述,JDK 1.7和JDK 1.8中的HashMap在底层数据结构、初始化与扩容、插入数据方式、Hash值计算、扩容策略、对null的处理以及节点和Hash值等方面存在显著差异。这些改进使得JDK 1.8中的HashMap在性能和功能上都有了显著提升,但仍然存在一些潜在的问题需要注意和解决。
标签:扩容,1.7,hashmap,JDK,真题,1.8,链表,HashMap From: https://blog.csdn.net/Chang_Yafei/article/details/142992224