赞
踩
在 Java 编程中,HashMap
和 Hashtable
是两个常用的哈希表实现,但它们在多个方面存在显著区别。了解这些区别对于编写高效且线程安全的代码至关重要。我们将通过源码分析来详细探讨这些差异。
HashMap
是非线程安全的,而 Hashtable
是线程安全的。这是由于 Hashtable
内部的许多方法都使用了 synchronized
关键字进行修饰,确保了线程安全性。
Hashtable
的线程安全实现以下是 Hashtable
中 put
方法的简化代码:
java
- public synchronized V put(K key, V value) {
- // Null key or value is not allowed
- if (key == null || value == null) {
- throw new NullPointerException();
- }
- // Hashing and insertion logic
- }
HashMap
的非线程安全实现相对应的,HashMap
的 put
方法没有使用 synchronized
关键字:
java
- public V put(K key, V value) {
- return putVal(hash(key), key, value, false, true);
- }
-
- final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
- // Hashing and insertion logic
- }
由于线程安全机制的开销,Hashtable
的效率通常低于 HashMap
。在多线程环境中,如果需要线程安全的哈希表,可以使用 ConcurrentHashMap
,其设计更加高效。
ConcurrentHashMap
的实现ConcurrentHashMap
通过分段锁(Segmented Locking)提高并发性能:
java
- public V put(K key, V value) {
- return putVal(key, value, false);
- }
-
- private V putVal(K key, V value, boolean onlyIfAbsent) {
- // Segment locking and insertion logic
- }
HashMap
允许一个 null
键和多个 null
值,而 Hashtable
不允许 null
键和 null
值。
HashMap
的实现示例java
- public V put(K key, V value) {
- if (key == null) {
- return putForNullKey(value);
- }
- // Normal insertion logic
- }
-
- private V putForNullKey(V value) {
- for (Node<K,V> e = table[0]; e != null; e = e.next) {
- if (e.key == null) {
- V oldValue = e.value;
- e.value = value;
- return oldValue;
- }
- }
- table[0] = new Node<>(0, null, value, table[0]);
- return null;
- }
Hashtable
的实现示例java
- public synchronized V put(K key, V value) {
- if (key == null || value == null) {
- throw new NullPointerException();
- }
- // Normal insertion logic
- }
HashMap
默认初始容量为 16,扩容时容量加倍。而 Hashtable
默认初始容量为 11,扩容时容量变为原来的 2 倍加 1。
HashMap
的容量计算HashMap
使用 tableSizeFor
方法确保容量为 2 的幂次方:
java
- static final int tableSizeFor(int cap) {
- int n = cap - 1;
- n |= n >>> 1;
- n |= n >>> 2;
- n |= n >>> 4;
- n |= n >>> 8;
- n |= n >>> 16;
- return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
- }
Hashtable
的扩容机制java
- protected void rehash() {
- int oldCapacity = table.length;
- Entry<?,?>[] oldMap = table;
-
- int newCapacity = (oldCapacity << 1) + 1;
- Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
-
- // Rehashing logic
- }
在 JDK 1.8 及以后的版本中,HashMap
在处理哈希冲突时,当链表长度超过阈值(默认为 8)时会将链表转换为红黑树。这大大提高了在发生哈希冲突时的查询效率。而 Hashtable
没有这种优化机制。
HashMap
红黑树转换逻辑java
- final void treeifyBin(Node<K,V>[] tab, int hash) {
- int n, index; Node<K,V> e;
- if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
- resize();
- else if ((e = tab[index = (n - 1) & hash]) != null) {
- TreeNode<K,V> hd = null, tl = null;
- do {
- TreeNode<K,V> p = replacementTreeNode(e, null);
- if (tl == null)
- hd = p;
- else {
- p.prev = tl;
- tl.next = p;
- }
- tl = p;
- } while ((e = e.next) != null);
- if ((tab[index] = hd) != null)
- hd.treeify(tab);
- }
- }
HashMap
和 Hashtable
各有优缺点。了解它们的实现细节可以帮助我们在实际应用中做出更明智的选择。通常,在单线程或并发读多于写的场景中使用 HashMap
,在多线程写操作频繁的场景中使用 ConcurrentHashMap
更为合适。Hashtable
因为其性能和设计上的限制,已逐渐被淘汰,不推荐在新代码中使用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。