面试题答案
一键面试线程安全性
- Hashtable:
- 源码实现:Hashtable的几乎所有方法(如
put
、get
等)都被synchronized
关键字修饰,这使得它是线程安全的。例如put
方法:
- 源码实现:Hashtable的几乎所有方法(如
public synchronized V put(K key, V value) {
// 实现逻辑
}
- 实际影响:在多线程环境下可以直接使用,无需额外同步措施。但由于锁粒度较大,在高并发场景下性能较低,线程竞争激烈时会导致大量线程等待。
- HashMap:
- 源码实现:HashMap的方法没有使用
synchronized
修饰,是非线程安全的。例如put
方法:
- 源码实现:HashMap的方法没有使用
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
- 实际影响:在单线程环境下性能较高。但在多线程环境中使用时,可能会出现数据不一致等问题,如在扩容时可能导致链表成环,需要使用
ConcurrentHashMap
等线程安全的集合替代,或者手动同步。
哈希算法
- Hashtable:
- 源码实现:直接使用对象的
hashCode
方法返回值作为哈希值,即int hash = key.hashCode();
。 - 实际影响:简单直接,但可能导致哈希冲突较多,尤其是当
hashCode
方法实现不理想时,会影响存储和查询效率。
- 源码实现:直接使用对象的
- HashMap:
- 源码实现:通过
(h = key.hashCode()) ^ (h >>> 16)
对hashCode
进一步处理,这种方式将高位和低位信息都融入哈希值计算,减少哈希冲突。例如hash
方法:
- 源码实现:通过
static final int hash(Object key) {
int h;
return (key == null)? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- 实际影响:在相同数据量下,哈希冲突相对较少,能提高哈希表的存储和查找效率。
扩容机制
- Hashtable:
- 源码实现:当
count >= threshold
(count
为已存储元素个数,threshold
为阈值)时进行扩容,扩容后的容量为原来的2倍 + 1。例如:
- 源码实现:当
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
int newCapacity = (oldCapacity << 1) + 1;
// 后续扩容逻辑
}
- 实际影响:扩容后容量增长较大,减少扩容频率,但可能造成空间浪费,特别是在数据量增长缓慢的情况下。
- HashMap:
- 源码实现:当
size >= threshold
(size
为已存储元素个数,threshold
为阈值)时扩容,扩容后的容量为原来的2倍。例如:
- 源码实现:当
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null)? 0 : oldTab.length;
int newCap = oldCap << 1;
// 后续扩容逻辑
}
- 实际影响:扩容后容量增长适中,既能及时应对数据增长,又不会过度浪费空间,在大多数场景下能较好平衡性能和空间。
键值对存储结构
- Hashtable:
- 源码实现:使用
Entry
数组存储键值对,Entry
类实现了Map.Entry
接口,采用链表法解决哈希冲突。例如:
- 源码实现:使用
private static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
// 其他方法
}
- 实际影响:在哈希冲突较多时,链表长度可能较长,查询性能会下降。
- HashMap:
- 源码实现:JDK 1.8 之前和Hashtable类似,使用
Entry
数组和链表。JDK 1.8 及之后,当链表长度大于等于8且数组长度大于等于64时,链表会转换为红黑树,以提高查询性能。例如:
- 源码实现:JDK 1.8 之前和Hashtable类似,使用
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
// 其他方法
}
- 实际影响:在哈希冲突较少时,链表结构性能较好;在哈希冲突较多时,红黑树结构能保持较好的查询性能,整体性能更优。
综上所述,在单线程或多线程但线程安全要求不高的场景,HashMap因其高性能而适用;在多线程且对线程安全要求严格的场景,Hashtable虽性能有限但可直接提供线程安全保障,不过在高并发场景下,ConcurrentHashMap
是更好的选择。