目录
40、HashMap 中的 key 我们可以使用任何类作为 key 吗?
基础篇
37、有数组了为什么还要搞个 ArrayList 呢?
通常我们在使用的时候,如果在不明确要插入多少数据的情况下,普通数组就很尴尬了,因为你不知道需要初始化数组大小为多少,而 ArrayList 可以使用默认的大小,当元素个数到达一定程度
后,会自动扩容。
可以这么来理解:我们常说的数组是定死的数组,ArrayList 却是动态数组。
38、说说什么是 fail-fast?
fail-fast 机制是 Java 集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生 fail-fast 事件。
例如:当某一个线程 A 通过 iterator 去遍历某集合的过程中,若该集合的内容被其他线程所改变
了,那么线程 A 访问集合时,就会抛出 ConcurrentModificationException 异常,产生 fail-fast 事件。这里的操作主要是指 add、remove 和 clear,对集合元素个数进行修改。
解决办法:建议使用“java.util.concurrent 包下的类”去取代“java.util 包下的类”。
可以这么理解:在遍历之前,把 modCount 记下来 expectModCount,后面 expectModCount 去和 modCount 进行比较,如果不相等了,证明已并发了,被修改了,于是抛出
ConcurrentModificationException 异常。
39、说说Hashtable 与 HashMap 的区别
本来不想这么写标题的,但是无奈,面试官都喜欢这么问 HashMap。
- 出生的版本不一样,Hashtable 出生于 Java 发布的第一版本 JDK 1.0,HashMap 出生于 JDK
1.2。
- 都实现了 Map、Cloneable、Serializable(当前 JDK 版本 1.8)。
- HashMap 继承的是 AbstractMap,并且 AbstractMap 也实现了 Map 接口。Hashtable 继承
Dictionary。
- Hashtable 中大部分 public 修饰普通方法都是 synchronized 字段修饰的,是线程安全的,
HashMap 是非线程安全的。
- Hashtable 的 key 不能为 null,value 也不能为 null,这个可以从 Hashtable 源码中的 put 方
法看到,判断如果 value 为 null 就直接抛出空指针异常,在 put 方法中计算 key 的 hash 值之
前并没有判断 key 为 null 的情况,那说明,这时候如果 key 为空,照样会抛出空指针异常。
- HashMap 的 key 和 value 都可以为 null。在计算 hash 值的时候,有判断,如果
key==null,则其 hash=0;至于 value 是否为 null,根本没有判断过。
- Hashtable 直接使用对象的 hash 值。hash 值是 JDK 根据对象的地址或者字符串或者数字算出
来的 int 类型的数值。然后再使用除留余数法来获得最终的位置。然而除法运算是非常耗费时
间的,效率很低。HashMap 为了提高计算效率,将哈希表的大小固定为了 2 的幂,这样在取
模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
- Hashtable、HashMap 都使用了 Iterator。而由于历史原因,Hashtable 还使用了
Enumeration 的方式。
- 默认情况下,初始容量不同,Hashtable 的初始长度是 11,之后每次扩充容量变为之前的
2n+1(n 为上一次的长度)而 HashMap 的初始长度为 16,之后每次扩充变为原来的两倍。
另外在 Hashtable 源码注释中有这么一句话:
Hashtable is synchronized. If a thread-safe implementation is not needed, it is recommended to use HashMap in place of Hashtable . If a thread-safe highly-
concurrent implementation is desired, then it is recommended to use
ConcurrentHashMap in place of Hashtable.
大致意思:Hashtable 是线程安全,推荐使用 HashMap 代替 Hashtable;如果需要线程安全高并发的话,推荐使用 ConcurrentHashMap 代替 Hashtable。
这个回答完了,面试官可能会继续问:HashMap 是线程不安全的,那么在需要线程安全的情况下还要考虑性能,有什么解决方式?
这里最好的选择就是 ConcurrentHashMap 了,但面试官肯定会叫你继续说一下
ConcurrentHashMap 数据结构以及底层原理等。
40、HashMap 中的 key 我们可以使用任何类作为 key 吗?
平时可能大家使用的最多的就是使用 String 作为 HashMap 的 key,但是现在我们想使用某个自定义类作为 HashMap 的 key,那就需要注意以下几点:
如果类重写了 equals 方法,它也应该重写 hashCode 方法。
类的所有实例需要遵循与 equals 和 hashCode 相关的规则。
如果一个类没有使用 equals,你不应该在 hashCode 中使用它。
咱们自定义 key 类的最佳实践是使之为不可变的,这样,hashCode 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode 和 equals 在未来不会改变,这样就会解决与可变相关的问题了。
41、HashMap 的长度为什么是 2 的 N 次方呢?
为了能让 HashMap 存数据和取数据的效率高,尽可能地减少 hash 值的碰撞,也就是说尽量把数据能均匀的分配,每个链表或者红黑树长度尽量相等。
我们首先可能会想到 % 取模的操作来实现。
下面是回答的重点哟:
取余(%)操作中如果除数是 2 的幂次,则等价于与其除数减一的与(&)操作(也就是说
hash % length == hash &(length - 1) 的前提是 length 是 2 的 n 次方)。并且,采用二进
制位操作 &,相对于 % 能够提高运算效率。
这就是为什么 HashMap 的长度需要 2 的 N 次方了。
42、HashMap 与 ConcurrentHashMap 的异同
- 都是 key-value 形式的存储数据;
- HashMap 是线程不安全的,ConcurrentHashMap 是 JUC 下的线程安全的;
- HashMap 底层数据结构是数组 + 链表(JDK 1.8 之前)。JDK 1.8 之后是数组 + 链表 + 红黑
树。当链表中元素个数达到 8 的时候,链表的查询速度不如红黑树快,链表会转为红黑树,红
黑树查询速度快;
- HashMap 初始数组大小为 16(默认),当出现扩容的时候,以 0.75 * 数组大小的方式进行扩
容;
- ConcurrentHashMap 在 JDK 1.8 之前是采用分段锁来现实的 Segment + HashEntry,
Segment 数组大小默认是 16,2 的 n 次方;JDK 1.8 之后,采用 Node + CAS + Synchronized
来保证并发安全进行实现。