Java面试题——HashMap相关面试题

8 篇文章 0 订阅
1 篇文章 0 订阅
本文详细解读HashMap的特性,包括快速存取、无序性和键唯一性,探讨其底层哈希表结构和解决哈希冲突的方法。同时,对比HashMap、LinkedHashMap和TreeMap的区别,以及put操作和扩容策略。
摘要由CSDN通过智能技术生成

1.谈一下HashMap的特性?

1.HashMap存储键值对实现快速存取,允许为null。key值不可重复,若key值重复则覆盖。
2.非同步,线程不安全。
3.底层是hash表,不保证有序(比如插入的顺序)

2.谈一下HashMap的底层原理是什么?

底层原理:Map + 无序 + 键唯一 + 哈希表 (数组+Entry)+ 存取值
1、HashMap是Map接口的实现类。实现HashMap对数据的操作,允许有一个null键,多个null值。 ConcurrentHashmap、Hashtable不支持key或者value为null,而HashMap是支持的。
2、是无序的集合,LinkedHashMap是有序的集合。
3、哈希表结构可以保证键唯一。
4、HashMap底层就是一个哈希表结构,数组+链表+红黑树(链表超过8个就用红黑树)。新建一个
HashMap的时候,就会初始化一个数组,数组的初始容量为16,数组中的每一项又是一个链表,
Entry就是数组中的元素,每个Entry其实就是一个key-value的键值对,它持有一个指向下一个元
素的引用next,这就构成了链表。先把元素按照相同的hash值进行分组,再把相同哈希值的元素挂
到一起。
5、hash算法决定在数组中的位置,equals方法决定在链表中的位置。当需要存储一个Node对象时
,会根据hash算法来决定在其数组中的位置,在根据equals方法决定其在该数组位置上的链表中的
存储位置;当需要取出一个Node对象时,也会根据hash算法找到其在数组中的存储位置, 在根据
equals方法从该位置上的链表中取出Node。

3 HashMap 的数据结构?

JDK1.7及之前:数组+链表
JDK1.8:数组+链表+红黑树

4 HashMap,LinkedHashMap,TreeMap 有什么区别?

HashMap 数据结构以数组为主,查询非常快
TreeMap 数据结构以红黑树为主,利用了红黑树左小右大的特点,可以实现 key 的排序,
LinkedHashMap 在 HashMap 的基础上增加了链表的结构,实现了插入顺序访问和最少访问

5谈一下hashMap中put是如何实现的?

1.计算关于key的hashcode值(与Key.hashCode的高16位做异或运算)
2.如果散列表为空时,调用resize()初始化散列表
3.如果没有发生碰撞,直接添加元素到散列表中去
4.如果发生了碰撞(hashCode值相同),进行三种判断
    4.1:若key地址相同或者equals后内容相同,则替换旧值
    4.2:如果是红黑树结构,就调用树的插入方法
    4.3:链表结构,循环遍历直到链表中某个节点为空,尾插法进行插入,插入之后判断链表个数是
    否到达变成红黑 树的阙值8;也可以遍历到有节点与插入元素的哈希值和内容相同,进行覆盖。
5.如果桶满了大于阀值,则resize进行扩容

6.HashMap中什么时候需要进行扩容,扩容resize()又是如何实现的?

(1)扩容时机:
1 初始化数组(jdk1.8之后,默认在第一次插入数据的时候才进行数组初始化,初始化
  是通过调用扩容方法实现的);
2 当元素个数超过临界值(临界值=装载因子*数组容量);
3 当链表长度超过默认阈值8,尝试转化为红黑树,但发现数组长度不到64时。
(2)扩容机制:
1 如果数组未初始化过,会将数组的容量和装载因子都设置为默认值,并将数组创建出来。
2 如果数组初始化过,扩容会分配一个新的数组,新的数组长度翻倍,然后遍历整个老结构将元素
重新哈希映射到新的数组里。
3 HashMap在进行扩容时,使用的重新哈希的方式非常巧妙,因为每次扩容都是翻倍,与原来计算的 
(数组长度-1)&hash 的结果相比,只是多了一个bit位,所以结点要么就在原来的位置,要么就被
分配到"原位置+旧容量"这个位置。 

7.什么是哈希冲突以及 哈希冲突的解决方案

当要存储一个数据的时候,首先用一个函数计算数据的地址,然后再将数据存进指定地址位置的数组里面。这个函数就是哈希函数,而这个数组就是哈希表。
哈希冲突是指哈希函数算出来的地址被别的元素占用了
1.开放定制法 
    (1)线性探测
   按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上往后加一个单位,直至不发生哈希冲突。 
  (2)再平方探测
   按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上先加1的平方个单位,若仍然存在则减1的平方个单位。随之是2的平方,3的平方等等。直至不发生哈希冲突。
  (3)伪随机探测
   按顺序决定值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来值的基础上加上随机数,直至不发生哈希冲突。
2.链地址法 
    对于相同的值,使用链表进行连接。使用数组存储每一个链表。
  优点:
  (1)拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
  (2)由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
  (3)开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
  (4)在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。  缺点:
  指针占用较大空间时,会造成空间浪费,若空间用于增大散列表规模进而提高开放地址法的效率。
3.公共溢出区法 
建立一个特殊存储空间,专门存放冲突的数据。此种方法适用于数据和冲突较少的情况。 
4.再散列法 
准备若干个hash函数,如果使用第一个hash函数发生了冲突,就使用第二个hash函数,第二个也冲突,使用第三个…… 
重点了解一下开放定制法和链地址法

8.谈一下当两个对象的hashCode相等时会怎么样?

会产生哈希碰撞,若key值相同则替换旧值,不然链接到链表后面,链表长度超过阙值8就转为红黑树存储

9.传统hashMap的缺点(为什么引入红黑树?)

JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分
布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 
HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了
它的优势。针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。

10 加载因子为什么是 0.75?

 那加载因子为什么是 0.75 而不是 0.5 或者 1.0 呢?
首先如果加载因子比较大,那么扩容发生的频率就比较低,但是他浪费的空间比较小,不过发生hash
冲突的几率就比较大,比如加载因子是1的时候,如果hashmap长度为128,那么可能hashmap的实际存
储元素数量在64至128之间的时间段比较多,而这个时间段发生hash冲突就比较大,造成数组中其中一
条链表较长,就会影响性能。而当加载因子值比较小的时候,扩容的频率就会变高,因此会占用更多的
空间,但是元素的存储就比较稀疏,发生哈希冲突的可能性就比较小,因此操作性能会比较高,比如设
置成0.5,同样128长度的hashmap,当数量达到65的时候就会触发hashmap的扩容,扩容后长度为256,
256里面只存储了65个似乎有点浪费了。所以综合了以上情况就取了一个 0.5 到 1.0 的平均数 0.75 
作为加载因子。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有鹿如溪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值