面试题答案
一键面试1. 哈希算法
- HashMap:
- 在JDK 1.8之前,HashMap使用
hashCode()
方法返回值直接参与哈希计算,通过(h = key.hashCode()) ^ (h >>> 16)
的方式进行扰动,让高位也参与到哈希计算,减少哈希冲突。 - JDK 1.8及之后,采用
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
来计算哈希值,对空键单独处理,其余基本相同。这样做可以让哈希分布更均匀,减少哈希冲突概率。
- 在JDK 1.8之前,HashMap使用
- HashTable:
直接使用
key.hashCode()
的返回值作为哈希值,没有像HashMap那样的扰动处理,这使得在哈希分布上可能不如HashMap均匀,更容易产生哈希冲突。
2. 扩容机制
- HashMap:
- 初始容量默认为16,负载因子默认为0.75。当
size >= threshold
(threshold = capacity * loadFactor
)时进行扩容。 - 扩容时,新容量为旧容量的2倍,即
newCapacity = oldCapacity << 1
。然后重新计算每个键值对在新数组中的位置。在JDK 1.8中,为了优化扩容性能,对于链表节点,如果在新容量下哈希值的高位不变,则节点位置不变,否则移动到新的位置。这种优化减少了重新计算哈希值和重新定位节点的开销。
- 初始容量默认为16,负载因子默认为0.75。当
- HashTable:
- 初始容量默认为11,负载因子默认为0.75。当
count >= threshold
(count
为已存储元素个数,threshold = capacity * loadFactor
)时进行扩容。 - 扩容时,新容量为旧容量的2倍加1,即
newCapacity = oldCapacity * 2 + 1
。同样需要重新计算每个键值对在新数组中的位置。与HashMap相比,扩容策略不同,且没有像HashMap JDK 1.8那样的优化机制,扩容开销相对较大。
- 初始容量默认为11,负载因子默认为0.75。当
3. 应用场景选择及举例
- HashMap:
- 适用场景:适用于单线程环境下,对性能要求较高且允许键值为null的场景。因为它在哈希算法和扩容机制上有一定优化,性能较好。
- 举例:在一个单线程的缓存系统中,用于存储缓存数据。由于单线程环境不存在线程安全问题,HashMap的性能优势可以充分发挥,提高缓存读写效率。
- HashTable:
- 适用场景:适用于多线程环境下,对线程安全有严格要求,不允许键值为null的场景。虽然性能相对HashMap较差,但线程安全的特性使其适用于多线程并发访问的情况。
- 举例:在一个多线程的银行转账系统中,用于记录账户信息等关键数据。由于涉及多线程并发操作,HashTable的线程安全机制可以保证数据的一致性和完整性,避免出现数据竞争问题。