HashMap
**许多情况下,面试者会在这个环节中出错,因为他们混淆了 hashCode()和 equals()方法。因为在此之前 hashCode()屡屡出现,而 equals()方法仅仅在获取值对象的时候才出现。一些优秀的开发者会指出使用不可变的、声明作 final 的对象,并且采用合适的 equals()和 hashCode()方法的话,将会减少碰撞的发生,提高效率。不可变性使得能够缓存不同键的 hashcode,这将提高整个获取对象的速度,使用 String,Interger 这样的 wrapper 类作为键是非常好的选择。**
**如果你认为到这里已经完结了,那么听到下面这个问题的时候,你会大吃一惊。“如果 HashMap 的大小超过了负载因子(load factor)定义的容量,怎么办?”除非你真正知道 HashMap 的工作原理,否则你将回答不出这道题。默认的负载因子大小为 0.75,也就是说,当一个 map 填满了 75%的 bucket 时候,和其它集合类(如 ArrayList 等)一样,将会创建原来 HashMap 大小的两倍的 bucket 数组,来重新调整 map 的大小,并将原来的对象放入新的 bucket 数组中。这个过程叫作 rehashing,因为它调用 hash 方法找到新的 bucket 位置。**
**如果你能够回答这道问题,下面的问题来了:“你了解重新调整 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 越来越多人用了。我们 **《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】** 知道 Hashtable 是 synchronized 的,但是 ConcurrentHashMap 同步性能更好,因为它仅仅根据同步级别对 map 的一部分进行上锁。ConcurrentHashMap 当然可以代替 HashTable,但是 HashTable 提供更强的线程安全性。看看**[**这篇博客**](()**查看 Hashtable 和 ConcurrentHashMap 的区别。**
**我个人很喜欢这个问题,因为这个问题的深度和广度,也不直接的涉及到不同的概念。让我们再来看看这些问题设计哪些知识点:**
**hashing****的概念**
**HashMap****中解决碰撞的方法**
**equals()****和 hashCode()的应用,以及它们在 HashMap 中的重要性**
**不可变对象的好处**
**HashMap****多线程的条件竞争**
**重新调整 HashMap 的大小**
**总结**
**HashMap****的工作原理**
**HashMap****基于 hashing 原理,我们通过 put()和 get()方法储存和获取对象。当我们将键值对传递给 put()方法时,它调用键对象的 hashCode()方法来计算 hashcode,让后找到 bucket 位置来储存值对象。当获取对象时,通过键对象的 equals()方法找到正确的键值对,然后返回值对象。HashMap 使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap 在每个链表节点中储存键值对对象。**
**当两个不同的键对象的 hashcode 相同时会发生什么? 它们会储存在同一个 bucket 位置的链表中。键对象的 equals()方法用来找到键值对。**
评论