集合(Map)

一、Map 知识结构及面试题目分析

在这里插入图片描述一般而言,Map 的面试题可分为四类:

HashMap 的数据结构,面试官考察的是对 Map 内部的存储结构了解;
HashMap 的增删查改操作,面试官考察的是对 map 内部操作流程的熟悉程度,既要知其然,还要知其所以然;
HashMap 的的应用,面试官考察的是灵活运用 HashMap 的能力;
其他 Map 类面试题,面试官考察的是系统掌握 Map 类的能力。

前两类面试题的考察点在深度,后两类面试题的考察点在广度。我们了解到面试题背后的逻辑后,可以在回答面试题时以小技巧的方式展示出自己掌握知识的深度和广度。

二、典型面试例题及思路分析

通常在一个比较典型的 Map 面试场景中,面试官会覆盖上述四个方向题目。

问题 1:"Java 集合你有了解么?平常项目比较常用的集合类有哪些?"

HashMap 和 ArrayList。

点评:

这是一个典型的开放式问题,答案并非是唯一的,而是与每个候选人的工作经历有关。

​通常面试官问这个问题是为了基于你的答案作进一步的考察,所以答案包括自己熟悉的集合类即可。当然这里的 “自己熟悉” 也是有一定技巧的:通常是回答一个 Map 实现类(HashMap)+ 一个 List 实现类(ArrayList),或者是一个 Map 实现类 (HashMap)。这样方便后续的展开回答。

问题 2:"那你知道 HashMap 内部的数据结构吗?"

各个版本的实现略有不同。JDK1.7 及以前的 HashMap 采用数组 + 链表的结构来存储数据; JDK8 中的 HashMap 采用了数组 + 链表或树的结构来存储数据。

点评:

​ 这是典型的第一类问题,主要考察点是候选人对 Map 内部结构的了解程度。

​ 一个典型的不那么合格的答案是:HashMap 的内部采用的是数据 + 链表的结构。

​这个问题在一定程度上并没有错,JDK1.7 及以前的版本的确是这种方式。但你的回答如果仅限于此的话,显然并不完备,毕竟 JDK1.8 自 2014 年 3 月发布以来已经 5 年多了,而且业界已经大规模应用。这个答案会向面试官传递出 “你对新技术的学习不够” 的信息,对 1~5 年工作经验的候选人来说,会让面试官觉得你的潜力和学习能力有所欠缺。

​但记得过犹不及,也不要在面试时炫技。所以 JDK8 什么时候采用链表,什么时候采用树结构,在这里就可以不展开了。如果面试官继续追问,可以继续回答。

问题 3:“HashMap 的存储数据的过程是什么样的?”

不同的 JDK 版本版本的存储过程略有差异。在 JDK1.8 中,HashMap 存储数据的过程可以分为以下几步:

对 key 的 hashCode () 进行 hash 后计算数组获得下标 index;
如果当前数组为 null,进行容量的初始化,初始容量为 16;
如果 hash 计算后没有碰撞,直接放到对应数组下标里;
如果 hash 计算后发生碰撞且节点已存在,则替换掉原来的对象;
如果 hash 计算后发生碰撞且节点已经是树结构,则挂载到树上。
如果 hash 计算后发生碰撞且节点是链表结构,则添加到链表尾,并判断链表是否需要转换成树结构(默认大于 8 的情况会转换成树结构);
完成 put 后,是否需要 resize () 操作(数据量超过 threshold,threshold 为初始容量和负载因子之积,默认为 12)。
而在 1.7 的版本中,5/6 是合在一起的,即如果发生哈希碰撞且节点是链表结构,则放在链表头。

点评:

​这是典型的第二类问题,主要考察的是 HashMap 的 put () 方法的源码熟悉程度。

​这类 HashMap 的 get ()/put ()/remove () 问题时,要把握两个方向:一是要清晰地说明整个操作过程;二是可以在细节上进行说明,表明自己对这块的掌握深度。比如说 JDK1.7 和 JDK1.8 的 put 过程的差异。

​上述这个 7 个步骤也不是一成不变的,可以通过看源码的方式来加深理解,或者总结为自己更容易理解的步骤。

面试 Tips

可以在回答时提及 HashMap 中的两类存储数据的方法:put () 和 putAll (),强调二者底层都是调用的同一个 putVal () 方法,然后再针对 putVal 展开回答。因为这个问题考察的是源码,所以可以在答案中可以加入一些可以体现你对源代码熟悉的细节。

问题 4:"如果 hashCode 相同,如何获取对象呢?"

hashCode 相同,说明这些对象的数据都在同一个数组下标对应的链表或者树上。get 方法的签名是 V get (Object key) ,入参只有一个 key,因此通过遍链表或者树,取出每一个节点对比 hash 值是否相等且 key 是否相等 (= 或者 equals)。

点评:

​这是第二类和第三类的结合问题,这里实际考察的是 get 方法的细节掌握。

​这个问题在 HashMap 在 public V get (Object key) 源代码中体现得很清晰,核心代码就是下面这段:在定位到数组位置后,就比较节点的哈希值与入参 key 的 hash 值和节点的 key 与入参 key 是否相等。

final Node<K,V> getNode(int hash, Object key) {

 Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
 if ((tab = table) != null && (n = tab.length) > 0 &&
     (first = tab[(n - 1) & hash]) != null) {
     // 数组该位置的第一个对象是目标对象的条件:hash值相等且key相等(=或者equals)
     if (first.hash == hash && // always check first node
         ((k = first.key) == key || (key != null && key.equals(k))))
         return first;
     //数组该位置的第一个对象不是目标对象,则遍历链表或者树
     if ((e = first.next) != null) {
           xxx;
         } while ((e = e.next) != null);
     }
 }
 return null;
}
问题 5:HashMap 和 HashTable 有什么区别?

​HashMap 是 JDK1.2 才出现的;HashTable 是 JDK1.0 就出现的。JDK 里面也说了 HashMap 可以大致相当于 HashTable(The HashMap class is roughly equivalent to Hashtable, except that it is
unsynchronized and permits nulls)。至于具体的差异:

1.HashMap 是线程不安全的,HashTable 是线程安全的。
2.HashMap 的键需要重新计算对象的 hash 值,而 HashTable 直接使用对象的 hashCode。
3.HashMap 的值和键都可以为 null,HashTable 的值和键都不能为 null。
4.HashMap 的数组的默认初始化大小为 16,HashTable 为 11;HashMap 扩容时会扩大两倍,HashTable 扩大两倍 + 1;

点评:

​这是典型的第 4 类问题,主要考察点是候选人对于集合类的整体掌握情况。

​这道题也有很多变种,比如说:

HashMap,LinkedHashMap,TreeMap 有什么区别?
Map 和 List 有什么区别?

这类题的回答要把握好两点:

一是每一个差异点不能错,如果某个差异点记不太清楚。那么宁愿不要回答也不要随便猜一个。因为这是基础题,不小心回答错误很容易给面试官留下基础不牢的印象。

二是不求回答出所有差异点,而求向 “面试官” 透露出自己真正了解和掌握这二者的区别,并非死记硬背。比如说答案中提及 HashMap 和 HashTable 在不同版本中的差异,这是向面试官传递出自己真正看过源代码的信息。又比如说,第 1 点的差异点可以改成 “HashMap 是线程不安全的,而 HashTable 由于所有方法都加了 synchronized 关键字所以是线程安全的” 等等,这些小细节上来体现出自己的熟悉度。
达到第一点,表示回答不出错;达到第二点,表示回答有一定出彩。

二、总结


总体而言,Map 类的面试题有如下特点:

属于基础知识的面试,主要考察点在 Java 基础;
几乎所有候选人都用过,但真正知道原理并有深入了解的侯选人并不太多
​因此,答案的第一要点是清晰准确,第二要点是通过前文所说的小技巧(说出版本差异、回答时提及原因等)向面试官来体现自己的深度。

三、扩展阅读

问:说说List,Set,Map三者的区别?

List(对付顺序的好帮手):List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象;

Set(注重独一无二的性质):不允许重复的集合。不会有多个元素引用相同的对象;

Map(用Key来搜索的专家):使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。

问:poll()方法和remove()方法区别?

poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。

问:为何Map接口不继承Collection接口?

尽管Map接口和它的实现也是集合框架的一部分,但Map不是集合,集合也不是Map。因此,Map继承Collection毫无意义,反之亦然。

如果Map继承Collection接口,那么元素去哪儿?Map包含key-value对,它提供抽取key或value列表集合的方法,但是它不适合“一组对象”规范。

问:我们能否使用任何类作为Map的key?

我们可以使用任何类作为Map的key,然而在使用它们之前,需要考虑以下几点:

(1)如果类重写了equals()方法,它也应该重写hashCode()方法。

(2)类的所有实例需要遵循与equals()和hashCode()相关的规则。请参考之前提到的这些规则。

(3)如果一个类没有使用equals(),你不应该在hashCode()中使用它。

(4)用户自定义key类的最佳实践是使之为不可变的,这样,hashCode()值可以被缓存起来,拥有更好的性能。不可变的类也可以确保hashCode()和equals()在未来不会改变,这样就会解决与可变相关的问题了。

比如,我有一个类MyKey,在HashMap中使用它。

//传递给MyKey的name参数被用于equals()和hashCode()中 MyKey key = new MyKey(‘Pankaj’); //assume hashCode=1234 myHashMap.put(key, ‘Value’); // 以下的代码会改变key的hashCode()和equals()值 key.setName(‘Amit’); //assume new hashCode=7890 //下面会返回null,因为HashMap会尝试查找存储同样索引的key,而key已被改变了,匹配失败,返回null myHashMap.get(new MyKey(‘Pankaj’)); 那就是为何String和Integer被作为HashMap的key大量使用。

问:Map接口提供了哪些不同的集合视图?

Map接口提供三个集合视图:

(1)Set keyset():返回map中包含的所有key的一个Set视图。集合是受map支持的,map的变化会在集合中反映出来,反之亦然。当一个迭代器正在遍历一个集合时,若map被修改了(除迭代器自身的移除操作以外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。

(2)Collection values():返回一个map中包含的所有value的一个Collection视图。这个collection受map支持的,map的变化会在collection中反映出来,反之亦然。当一个迭代器正在遍历一个collection时,若map被修改了(除迭代器自身的移除操作以外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。

(3)Set<Map.Entry<K,V>> entrySet():返回一个map钟包含的所有映射的一个集合视图。这个集合受map支持的,map的变化会在collection中反映出来,反之亦然。当一个迭代器正在遍历一个集合时,若map被修改了(除迭代器自身的移除操作,以及对迭代器返回的entry进行setValue外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。

问:如何决定选用HashMap还是TreeMap?

对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。

问:TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?

TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java中对函数式编程的支持)。

问:HashMap,LinkedHashMap,TreeMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap的区别?

HashMap基于散列表来的实现,即使用hashCode()进行快速查询元素的位置,显著提高性能。插入和查询“键值对”的开销是固定的。可以通过设置容量和负载因子,以调整容器的性能。
LinkedHashMap, 类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入的次序,只比HashMap慢一点。而在迭代访问时反而更快,因为它使用链表维护内部次序。
TreeMap, 是基于红黑树的实现。实现了SortedMap,SortedMap 可以确保键处于排序状态。所以查看“键”和“键值对”时,所有得到的结果都是经过排序的,次序由Comparable或Comparator决定。SortedMap拥有其他额外的功能,如:Comparator comparator()返回当前Map使用的Comparator或者null. T firstKey() 返回Map的第一个键,T lastKey() 返回最后一个键。SortedMap headMap(toKey),生成一个键小于toKey的Map子集。SortedMap tailMap(fromKey) 也是生成一个子集。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树
WeakHashMap表示弱键映射,允许释放映射所指向的对象。这是为了解决某类特殊问题而设计的,如果映射之外没有引用指向某个“键”,则“键”可以被垃圾收集器回收。
ConcurrentHashMap一种线程安全的Map,它不涉及同步加锁。
IdentityHashMap使用==代替equals() 对“键”进行比较的散列映射。专为解决特殊问题而设计。

问:HashMap 和 HashSet区别是什么?

如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了clone()、writeObject()、readObject()是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。

HashMap HashSet
实现了Map接口 实现Set接口
存储键值对 仅存储对象
调用 put()向map中添加元素 调用 add()方法向Set中添加元素
HashMap使用键(Key)计算Hashcode HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,

问:HashSet如何检查重复

当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。

问:WeakHashMap与HashMap的区别是什么?

WeakHashMap 的工作与正常的 HashMap 类似,但是使用弱引用作为 key,意思就是当 key 对象没有任何引用时,key/value 将会被回收。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值