HashMap的面试题

参考链接: http://www.importnew.com/7099.html
http://blog.csdn.net/w_fenghui/article/details/2010387

HashMap的工作原理是近年来常见的Java面试题。几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此特殊呢?是因为这道题考察的深度很深。这题经常出现在高级或中高级面试中。投资银行更喜欢问这个问题,甚至会要求你实现HashMap来考察你的编程能力。ConcurrentHashMap和其它同步集合的引入让这道题变得更加复杂。

1,你用过HashMap吗?” “什么是HashMap?你为什么用到它?

一个通俗的例子是,为了查找电话簿中某人的号码,可以创建一个按照人名首字母顺序排列的表
回答HashMap的一些特性,譬如HashMap可以接受null键值和值,而Hashtable则不能;HashMap是非synchronized;HashMap很快;以及HashMap储存的是键值对等等。这显示出你已经用过HashMap,而且对它相当的熟悉。

2,“你知道HashMap的工作原理吗?” “你知道HashMap的get()方法的工作原理吗?”

HashMap是基于hashing的原理,HashMap是在bucket中储存键对象和值对象,作为Map.Entry。

3,“当两个对象的hashcode相同会发生什么?”

“因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。”

4,“如果两个键的hashcode相同,你如何获取值对象?

面试者会回答:当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置

找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。

4.1 解决Hash碰撞的方法

  1. 开放地址法
    2.链表法
  2. 再哈希 — 上次散列计算发生碰撞时,再利用该次碰撞的地址产生新的散列地址。
  3. 建立一个公共溢出区

HashMap 中是通过 链表法解决hash 碰撞的。然而当元素的个数大于槽位数时,必然会导致一个位置有多个元素(PS:HashMap 中table 是动态扩容,所以没有这个)。此时可以选择开放寻址,所有的元素都存放在散列表中(前提是数组支持动态扩容)。

1,开放地址法 :

Hi=(H(key)+di) MOD m , i=1,2,…,k(k<=m-1); 其中Hi 是散列的地址,H(Key) 是计算出的key 的 hash值。 di 是增量地址。
当Hi 计算出有碰撞时,H(Key) 就向后移动 di 个位置,这个过程称为探测
首先计算出元素的直接哈希地址 H ( key ) ,如果该存储单元已被其他元素占用,则继续查看地址为 H ( key ) + d 2 的存储单元,如此重复直至找到某个存储单元为空时,将关键字为 key 的数据元素存放到该单元。
根据 di 的值可以分为线性探测再散列, 二次探测在散列(移动位置是2 的指数),伪随机探测再散列。

一个线性探测再散列的例子
设有哈希函数 H ( key ) = key mod 7 ,哈希表的地址空间为 0 ~ 6 ,对关键字序列( 32 , 13 , 49 , 55 , 22 , 38 , 21 )按线性探测再散列和二次探测再散列的方法分别构造哈希表。

32 % 7 = 4 ; 13 % 7 = 6 ; 49 % 7 = 0 ;
55%7 = 6 发生冲突,下一个存储地址 (6+1)%7 =0 仍然发生冲突,再下一个存储地址(6+2)%7 = 1 【这里可以是(55+2)%7 = (6+2)%7,使用第一次哈希后的余数进行递增就可以,而如果每次使用上一次的哈希结果,自然只能是加1 即 (0+1)%7】,未发生冲突,可以存入。
22 % 7 = 1 发生冲突,下一个存储地址是:( 1 + 1 )% 7 = 2 未发生冲突;
38 % 7 = 3 ;
21 % 7 = 0 发生冲突,按照上面方法继续探测直至空间 5 ,不发生冲突

2,拉链法

冲突的key 以链表的形式存储
与开放寻址相比 优点:

  • 处理冲突简单,非同义词不会发生冲突,平均查找长度较短。
  • 链表节点是动态申请的,适合无法确定表长的情况
  • 开放寻址为了减少冲突,要求填装因子很小,会浪费空间
  • 拉链法构造的散列表中,节点删除操作简单。开放寻址中不只能讲需要删除的节点标记,不可以真正删除。因为空地址单元是查找失败的条件

4.2 Hash 攻击

通过请求大量key 不同但是hashCode相同的数据,使HashMap 不断的发生碰撞,变成一个singleLinkedList。
这样put get 性能就从O(1) 变成了O(n)。
Java8 中通过使用TreeMap ,可以一定程度上防御Hash攻击。

这里写图片描述

4.3,如何减少碰撞的发生

影响产生冲突的多少有三个因素:
Hash函数计算出来的是否均匀
处理充足的方法
散列表的负荷因子
因此选择合适的hash函数。同时选择合适的对象作为Key ,使得key的hashCode()函数计算不同的key 可以得到不同的hash值。这里可以是String Integer等包装对象,或者自定义的对象时hashCode() 函数写好。

对于开放定址法,荷载因子是特别重要因素,应严格限制在0.7-0.8以下。超过0.8,查表时的CPU缓存不命中(cache missing)按照指数曲线上升

5,“如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?”

“你了解重新调整HashMap大小存在什么问题吗?”你可能回答不上来,这时面试官会提醒你当多线程的情况下,可能产生条件竞争(race condition)。

当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。????为什么。这个时候,你可以质问面试官,为什么这么奇怪,要在多线程的环境下使用HashMap呢?:)

热心的读者贡献了更多的关于HashMap的问题:

为什么String, Interger这样的wrapper类适合作为键?

String, Interger这样的wrapper类作为HashMap的键是再适合不过了,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个field声明成final就能保证hashCode是不变的,那么请这么做吧。因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。

我们可以使用自定义的对象作为键吗?

这是前一个问题的延伸。当然你可能使用任何对象作为键,只要它遵守了equals()和hashCode()方法的定义规则,并且当对象插入到Map中之后将不会再改变了。如果这个自定义对象时不可变的,那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了。

我们可以使用CocurrentHashMap来代替Hashtable吗?

这是另外一个很热门的面试题,因为ConcurrentHashMap越来越多人用了。我们知道Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性。

HashTable 与HashMap 以及TreeMap的区别

我个人很喜欢这个问题,因为这个问题的深度和广度,也不直接的涉及到不同的概念。让我们再来看看这些问题设计哪些知识点:

hashing的概念
HashMap中解决碰撞的方法
equals()和hashCode()的应用,以及它们在HashMap中的重要性
不可变对象的好处
HashMap多线程的条件竞争
重新调整HashMap的大小

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值