HashMap面试题

1、为什么重写Equals还要重写HashCode方法?

        因为,在没有进行方法重写的时候,equals方法底层调用==,比较的是对象的内存地址,hashcode方法根据对象内存地址转换成整数类型。
        所以,①在HashMap中比较两个对象是否相等,根据内存地址肯定不合适,所以必须重写equals方法。②两个对象相等,hash值一定相等,所以hashcode方法也必须重写。
        另外,阿里巴巴开发手册中也要求:只要覆写 equals,就必须覆写 hashCode。  
        并且,HashMap集合自定义对象作为key的时候能够避免内存泄露问题。

2、HashMap如何避免内存泄漏问题?

       内存泄漏:内存堆积,无法释放资源,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
       出现内存泄漏的情况:如果两个对象,new的是同一个类,内容相同,但是没有重写equals和hashcode方法,比较结果是不相等的,在HashMap 中存储的时候会导致内存堆积而造成内存泄漏问题。

自定义对象作为key的时候,重写equals方法和hashcode方法,确保key不重复创建。

3、HashMap1.7底层如何实现?

基于数组+单向链表。

4、HashMap Key为null存放在什么位置?

key可以为null,存放在index=0的位置。

5、HashMap 与 HashTable 的区别?

引导面试官进入多线程、锁、ConcurrentHashMap 

  1)key是否允许为null:HashMap 允许 ,HashTable 不允许。
  当 get() 方法返回 null 值时,既可以表示 HashMap 中没有该键,也可以表示该键所对应的值为 null 。因此,在 HashMap 中不能由 get() 方法来判断。
    HashMap 中是否存在某个键, 而应该用 containsKey() 方法来判断。

  2)Hash计算方式:HashTable直接使用对象的hashCode,而HashMap重新计算hash值。

  3)线程安全性:HashMap 线程不安全 ;HashTable线程安全,采用 synchronized锁

  在多线程的情况下,推荐使用 ConcurrentHashMap 线程安全 且效率非常高。

6、HashMap如何解决Hash冲突问题?

  Hash冲突:两个不同hash值的key,最终经过计算后会落到同一个数组下标,产生冲突。
  解决:HashMap1.7  数组 + 单向链表;HashMap1.8 数组 + 单向链表 + 红黑树。

7、HashMap 底层是如何实现的?       

  HashMap1.7 版本中,底层是基于数组+链表实现的,如果发生Hash 冲突概率问题,会存放到同一个链表中,链表如果过长 会从头查询到尾部效率非常低。

  HashMap1.8 版本 (数组容量>=64&链表长度大于8)就会将该链表转化红黑树。

8、HashMap 根据 Key 查询时间复杂度

  1) Key 没有产生 hash 冲突 时间复杂度是为 o(1), 只需要查询一次

  2) Key 产生 hash 冲突 采用链表存放则为 O(N) 从头查询到尾部

  3) key 产生 hash 冲突采用红黑树存放则为 O(LogN)

9、HashMap 底层是有序存放的吗?

  HashMap 底层无序存放,因为 Hash 算法是散列计算的 没有顺序。

  遍历所有key的原理:遍历的时候从数组0开始遍历每个链表O(N)和红黑树O(logN),遍历结果存储顺序不保证一致。

  LinkedHashMap基于双向链表实现。

10、HashMap7 扩容产生死循环问题有了解过吗?

因为 HashMap 本身是线程不安全的,在多线程扩容的的情况下,再进行查询会发生死循环问题。因为底层数组下标的链表会形成一个环形链表,故而在查询时,如果链表中不存在该key,就会发生死循。

11、HashMap1.7与1.8有哪些区别?

  1)数据结构:jdk7:数组+链表;jdk8:数组+链表+红黑树

       2)Put方法原理:Jdk7采用头插法,写法简单,有多线程死循环问题;jdk8采用尾插法,写法复杂,解决多线程死循环问题。

       3)Hash冲突:jdk7取模HashCode,jdk8重算hash,降低hash冲突概率,提高查询效率。

       4)扩容原理:JDK8将新数组链表分为高位和低位,不用重新计算hash,效率更高。

12、为什么加载因子是0.75而不是1?

需要从查询效率空间利用率上进行考虑:

  如果加载因子越大,空间利用率比较高,有可能冲突概率越大,查询效率越低;

  如果加载因子越小,有可能冲突概率比较小,空间利用率不高;

  通过分布式统计算法得出来的因子值为0.75

13、HashMap1.8底层如何降低hash冲突概率?

       通过对hashcode进行异或运算、与运算,增加散列值随机性,hash冲突概率更小。
       首先获取数组的长度,进行length-1,再进行&运算。length-1的二进制形式进行与运算后可以让key分布在数组不同的下标位置,使得分布更均匀,hash冲突概率更低。

  HashMap1.7中对HashCode取模会导致hash冲突概率变大。故HashMap1.8中采取了hash函数的计算,通过n-1降低hash冲突的概率。

  核心代码:(h=key.hashCode())^h>>>16 ; i = (n-1)&h;   h(hash)   n(length)

  在HashMap1.8中数组的长度按规定一定是2的幂次方。数组的长度的二进制形式是:10000000,length-1二进制形式就是01111111。

  &(与运算) 针对二进制,00得0, 11得1, 10 得0。

  各key的hash值与length-1进行&运算时,会被分配在数据组不同得位置,如此使分布更为均匀。

14、为什么不直接将key作为hash而是与高16位做亦或运算?

HashMap1.8中对key做高16位亦或运算,增加了散列值的随机性,降低了hash冲突的可能性。

  如果直接使用哈希值其发生冲突的可能性增加,而HashMap中通过与高16位做异或操作将低位与高位进行混合,这样增加了散列值的随机性,降低了hash冲突的可能性。

15、为什么多线程情况下不推荐使用HashMap、HashTable ?

HashMap线程不安全,HashTable效率低。多线程情况下推荐使用ConcurrentHashMap。

       HashMap属于线程不安全集合;
       HashTable底层使用Sychronized锁保证线程安全性问题,但是将整个数组锁住,只能有一个线程调用get或put方法,如果没有获取到锁的线程有可能变为阻塞等待状态。效率非常低,将多线程put操作变为单线程。建议采用ConcurrentHashMap分段锁机制。

16、modCount的作用

  HashMap 不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出异常
        modCount 顾名思义就是修改次数,对HashMap 内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount(期望修改次数)。在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,如果不相等就表示已经有其他线程修改了 Map。

17、HashMap1.8扩容底层原理

        整个扩容过程,没有重新计算Hash值。将新链表分为高位和低位。将链表中元素的hash值与原长度进行&运算,结果为0的为低位,存放index位置与原来index一致;结果为1的为高位,存放index位置为原index+oldCap。

  if ((e.hash & oldCap) == 0) { 由于oldCap原来的容量没有减去1 所以所有的hash&oldCap为0或者1}

18、HashMap如何存放1万条key效率最高

  正常存放1万个key的情况下大概扩容10次。多次扩容严重影响性能,所以当存放1万条key时,需要提前对数组大小进行初始化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值