HashMap 与 Hashtable 深度对比分析
目录
- 第一章:基础概念对比
- 第二章:Hash 计算方法
- 第三章:初始容量和扩容机制
- 第四章:线程安全性对比
- 第五章:性能对比
- 第六章:实际代码演示
第一章:基础概念对比
1. HashMap vs Hashtable 基本对比
特性 | HashMap | Hashtable |
---|---|---|
线程安全 | 非线程安全 | 线程安全 |
继承关系 | 继承自 AbstractMap | 继承自 Dictionary |
null 值 | 允许 null 键和 null 值 | 不允许 null 键和 null 值 |
迭代器 | Fail-fast 迭代器 | Enumerator 迭代器 |
性能 | 性能更好 | 性能较差(同步开销) |
推荐使用 | 推荐使用 | 不推荐使用 |
2. 类结构对比
HashMap 类结构:
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {// 默认初始容量static final int DEFAULT_INITIAL_CAPACITY = 16;// 最大容量static final int MAXIMUM_CAPACITY = 1 << 30;// 默认负载因子static final float DEFAULT_LOAD_FACTOR = 0.75f;// 树化阈值static final int TREEIFY_THRESHOLD = 8;// 反树化阈值static final int UNTREEIFY_THRESHOLD = 6;// 最小树化容量static final int MIN_TREEIFY_CAPACITY = 64;
}
Hashtable 类结构:
public class Hashtable<K,V> extends Dictionary<K,V>implements Map<K,V>, Cloneable, java.io.Serializable {// 默认初始容量private static final int DEFAULT_INITIAL_CAPACITY = 11;// 默认负载因子private static final float DEFAULT_LOAD_FACTOR = 0.75f;// 最大容量private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}
第二章:Hash 计算方法
1. HashMap 的 Hash 计算
JDK 8 及以后版本:
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
Hash 计算过程:
- 获取 key 的 hashCode
- 将 hashCode 右移 16 位
- 与原 hashCode 进行异或运算
- 如果 key 为 null,返回 0
示例:
String key = "hello";
int hashCode = key.hashCode(); // 假设为 99162322
int hash = hashCode ^ (hashCode >>> 16); // 99162322 ^ 1512 = 99160810
JDK 7 及以前版本:
static int hash(int h) {h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);
}
2. Hashtable 的 Hash 计算
Hashtable 的 Hash 计算:
private int hash(Object k) {return hashSeed ^ k.hashCode();
}
Hash 计算过程:
- 获取 key 的 hashCode
- 与 hashSeed 进行异或运算
- 如果 key 为 null,抛出 NullPointerException
示例:
String key = "hello";
int hashCode = key.hashCode(); // 假设为 99162322
int hash = hashSeed ^ hashCode; // 0 ^ 99162322 = 99162322
3. Hash 计算对比分析
特性 | HashMap | Hashtable |
---|---|---|
扰动函数 | 使用右移和异或 | 使用 hashSeed |
null 处理 | 返回 0 | 抛出异常 |
计算复杂度 | 简单 | 简单 |
分布均匀性 | 更好 | 一般 |
第三章:初始容量和扩容机制
1. HashMap 的初始容量和扩容
初始容量:
// 默认初始容量
static final int DEFAULT_INITIAL_CAPACITY = 16;// 构造函数
public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // 0.75f
}public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);
}public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " + loadFactor);this.loadFactor = loadFactor;this.threshold = tableSizeFor(initialCapacity);
}
容量计算:
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;
}
扩容机制:
final Node<K,V>[] resize() {Node<K,V>[] oldTab = table;int oldCap = (oldTab == null) ? 0 : oldTab.length;int oldThr = threshold;int newCap, newThr = 0;if (oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // 双倍扩容}else if (oldThr > 0)newCap = oldThr;else {newCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}threshold = newThr;Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;if (oldTab != null) {for (int j = 0; j < oldCap; ++j) {Node<K,V> e;if ((e = oldTab[j]) != null) {oldTab[j] = null;if (e.next == null)newTab[e.hash & (newCap - 1)] = e;else if (e instanceof TreeNode)((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else {Node<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;do {next = e.next;if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;
}
2. Hashtable 的初始容量和扩容
初始容量:
// 默认初始容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;// 构造函数
public Hashtable() {this(11, 0.75f);
}public Hashtable(int initialCapacity) {this(initialCapacity, 0.75f);
}public Hashtable(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal Load: " + loadFactor);if (initialCapacity == 0)initialCapacity = 1;this.loadFactor = loadFactor;table = new Entry<?,?>[initialCapacity];threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
扩容机制:
protected void rehash() {int oldCapacity = table.length;Entry<?,?>[] oldMap = table;int newCapacity = (oldCapacity << 1) + 1;if (newCapacity - MAX_ARRAY_SIZE > 0) {if (oldCapacity == MAX_ARRAY_SIZE)return;newCapacity = MAX_ARRAY_SIZE;}Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];modCount++;threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);table = newMap;for (int i = oldCapacity; i-- > 0;) {for (Entry<K,V> old = (Entry<K,V>)oldMap[i]; old != null;) {Entry<K,V> e = old;old = old.next;int index = (e.hash & 0x7FFFFFFF) % newCapacity;e.next = (Entry<K,V>)newMap[index];newMap[index] = e;}}
}
3. 初始容量和扩容对比
特性 | HashMap | Hashtable |
---|---|---|
默认初始容量 | 16 | 11 |
容量计算 | 2 的幂次 | 任意正整数 |
扩容方式 | 双倍扩容 | 2n+1 扩容 |
扩容时机 | 负载因子 0.75 | 负载因子 0.75 |
最大容量 | 2^30 | Integer.MAX_VALUE - 8 |
第四章:线程安全性对比
1. HashMap 的线程安全性
HashMap 是非线程安全的:
// 非线程安全的操作
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}public V get(Object key) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? null : e.value;
}
并发问题:
- 数据丢失:多个线程同时 put 可能导致数据丢失
- 死循环:JDK 7 及以前版本在扩容时可能产生死循环
- 数据不一致:读取到不完整的数据
2. Hashtable 的线程安全性
Hashtable 是线程安全的:
public synchronized V put(K key, V value) {// 实现细节
}public synchronized V get(Object key) {// 实现细节
}public synchronized V remove(Object key) {// 实现细节
}
同步机制:
- 所有公共方法都使用
synchronized
关键字 - 整个方法级别的同步
- 性能较差,但保证线程安全
3. 线程安全性对比
特性 | HashMap | Hashtable |
---|---|---|
线程安全 | 否 | 是 |
同步方式 | 无 | synchronized 方法 |
性能 | 高 | 低 |
并发问题 | 有 | 无 |
推荐使用 | 单线程环境 | 多线程环境(不推荐) |
第五章:性能对比
1. 性能测试代码
public class HashMapVsHashtablePerformance {private static final int TEST_SIZE = 1000000;public static void main(String[] args) {System.out.println("=== HashMap vs Hashtable 性能对比 ===\n");// 测试 HashMaptestHashMap();// 测试 HashtabletestHashtable();// 测试并发性能testConcurrentPerformance();}private static void testHashMap() {System.out.println("--- HashMap 性能测试 ---");Map<String, Integer> map = new HashMap<>();// 测试 put 操作long startTime = System.currentTimeMillis();for (int i = 0; i < TEST_SIZE; i++) {map.put("key" + i, i);}long putTime = System.currentTimeMillis() - startTime;// 测试 get 操作startTime = System.currentTimeMillis();for (int i = 0; i < TEST_SIZE; i++) {map.get("key" + i);}long getTime = System.currentTimeMillis() - startTime;System.out.println("HashMap put 操作耗时: " + putTime + "ms");System.out.println("HashMap get 操作耗时: " + getTime + "ms");System.out.println("HashMap 总耗时: " + (putTime + getTime) + "ms");System.out.println();}private static void testHashtable() {System.out.println("--- Hashtable 性能测试 ---");Map<String, Integer> map = new Hashtable<>();// 测试 put 操作long startTime = System.currentTimeMillis();for (int i = 0; i < TEST_SIZE; i++) {map.put("key" + i, i);}long putTime = System.currentTimeMillis() - startTime;// 测试 get 操作startTime = System.currentTimeMillis();for (int i = 0; i < TEST_SIZE; i++) {map.get("key" + i);}long getTime = System.currentTimeMillis() - startTime;System.out.println("Hashtable put 操作耗时: " + putTime + "ms");System.out.println("Hashtable get 操作耗时: " + getTime + "ms");System.out.println("Hashtable 总耗时: " + (putTime + getTime) + "ms");System.out.println();}private static void testConcurrentPerformance() {System.out.println("--- 并发性能测试 ---");// 测试 HashMap 并发性能testHashMapConcurrent();// 测试 Hashtable 并发性能testHashtableConcurrent();}private static void testHashMapConcurrent() {System.out.println("--- HashMap 并发测试 ---");Map<String, Integer> map = new HashMap<>();int threadCount = 10;int operationsPerThread = TEST_SIZE / threadCount;long startTime = System.currentTimeMillis();Thread[] threads = new Thread[threadCount];for (int i = 0; i < threadCount; i++) {final int threadId = i;threads[i] = new Thread(() -> {for (int j = 0; j < operationsPerThread; j++) {map.put("key" + (threadId * operationsPerThread + j), j);}});threads[i].start();}for (Thread thread : threads) {try {thread.join();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}long concurrentTime = System.currentTimeMillis() - startTime;System.out.println("HashMap 并发操作耗时: " + concurrentTime + "ms");System.out.println("HashMap 最终大小: " + map.size());System.out.println();}private static void testHashtableConcurrent() {System.out.println("--- Hashtable 并发测试 ---");Map<String, Integer> map = new Hashtable<>();int threadCount = 10;int operationsPerThread = TEST_SIZE / threadCount;long startTime = System.currentTimeMillis();Thread[] threads = new Thread[threadCount];for (int i = 0; i < threadCount; i++) {final int threadId = i;threads[i] = new Thread(() -> {for (int j = 0; j < operationsPerThread; j++) {map.put("key" + (threadId * operationsPerThread + j), j);}});threads[i].start();}for (Thread thread : threads) {try {thread.join();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}long concurrentTime = System.currentTimeMillis() - startTime;System.out.println("Hashtable 并发操作耗时: " + concurrentTime + "ms");System.out.println("Hashtable 最终大小: " + map.size());System.out.println();}
}
2. 性能对比总结
性能指标 | HashMap | Hashtable |
---|---|---|
单线程性能 | 优秀 | 良好 |
多线程性能 | 差(数据不安全) | 良好(但性能较低) |
内存使用 | 较少 | 较多 |
CPU 使用 | 较少 | 较多 |
推荐场景 | 单线程环境 | 多线程环境(不推荐) |
第六章:实际代码演示
1. 完整的对比演示
public class HashMapHashtableDemo {public static void main(String[] args) {System.out.println("=== HashMap vs Hashtable 完整对比演示 ===\n");// 1. 基本操作对比basicOperationsComparison();// 2. null 值处理对比nullValueHandlingComparison();// 3. 线程安全性对比threadSafetyComparison();// 4. 性能对比performanceComparison();}private static void basicOperationsComparison() {System.out.println("=== 基本操作对比 ===");// HashMap 操作System.out.println("--- HashMap 操作 ---");Map<String, Integer> hashMap = new HashMap<>();hashMap.put("key1", 1);hashMap.put("key2", 2);hashMap.put("key3", 3);System.out.println("HashMap 内容: " + hashMap);System.out.println("HashMap 大小: " + hashMap.size());System.out.println("HashMap 获取 key1: " + hashMap.get("key1"));// Hashtable 操作System.out.println("\n--- Hashtable 操作 ---");Map<String, Integer> hashtable = new Hashtable<>();hashtable.put("key1", 1);hashtable.put("key2", 2);hashtable.put("key3", 3);System.out.println("Hashtable 内容: " + hashtable);System.out.println("Hashtable 大小: " + hashtable.size());System.out.println("Hashtable 获取 key1: " + hashtable.get("key1"));System.out.println();}private static void nullValueHandlingComparison() {System.out.println("=== null 值处理对比 ===");// HashMap null 值处理System.out.println("--- HashMap null 值处理 ---");Map<String, Integer> hashMap = new HashMap<>();try {hashMap.put(null, 1);hashMap.put("key", null);System.out.println("HashMap 支持 null 键和 null 值");System.out.println("HashMap null 键值: " + hashMap.get(null));System.out.println("HashMap key 值: " + hashMap.get("key"));} catch (Exception e) {System.err.println("HashMap null 值处理异常: " + e.getMessage());}// Hashtable null 值处理System.out.println("\n--- Hashtable null 值处理 ---");Map<String, Integer> hashtable = new Hashtable<>();try {hashtable.put(null, 1);System.err.println("Hashtable 不应该支持 null 键");} catch (Exception e) {System.out.println("Hashtable null 键异常: " + e.getMessage());}try {hashtable.put("key", null);System.err.println("Hashtable 不应该支持 null 值");} catch (Exception e) {System.out.println("Hashtable null 值异常: " + e.getMessage());}System.out.println();}private static void threadSafetyComparison() {System.out.println("=== 线程安全性对比 ===");// HashMap 线程安全性测试System.out.println("--- HashMap 线程安全性测试 ---");Map<String, Integer> hashMap = new HashMap<>();testConcurrentAccess(hashMap, "HashMap");// Hashtable 线程安全性测试System.out.println("\n--- Hashtable 线程安全性测试 ---");Map<String, Integer> hashtable = new Hashtable<>();testConcurrentAccess(hashtable, "Hashtable");System.out.println();}private static void testConcurrentAccess(Map<String, Integer> map, String mapType) {int threadCount = 10;int operationsPerThread = 1000;CountDownLatch latch = new CountDownLatch(threadCount);long startTime = System.currentTimeMillis();for (int i = 0; i < threadCount; i++) {final int threadId = i;new Thread(() -> {try {for (int j = 0; j < operationsPerThread; j++) {String key = "key" + (threadId * operationsPerThread + j);map.put(key, j);map.get(key);}} finally {latch.countDown();}}).start();}try {latch.await();} catch (InterruptedException e) {Thread.currentThread().interrupt();}long endTime = System.currentTimeMillis();System.out.println(mapType + " 并发操作耗时: " + (endTime - startTime) + "ms");System.out.println(mapType + " 最终大小: " + map.size());}private static void performanceComparison() {System.out.println("=== 性能对比 ===");int testSize = 100000;// HashMap 性能测试long hashMapTime = testPerformance(new HashMap<>(), testSize, "HashMap");// Hashtable 性能测试long hashtableTime = testPerformance(new Hashtable<>(), testSize, "Hashtable");System.out.println("性能对比结果:");System.out.println("HashMap 耗时: " + hashMapTime + "ms");System.out.println("Hashtable 耗时: " + hashtableTime + "ms");System.out.println("性能差异: " + (hashtableTime - hashMapTime) + "ms");System.out.println("性能提升: " + String.format("%.2f%%", (double)(hashtableTime - hashMapTime) / hashtableTime * 100));}private static long testPerformance(Map<String, Integer> map, int testSize, String mapType) {long startTime = System.currentTimeMillis();// 测试 put 操作for (int i = 0; i < testSize; i++) {map.put("key" + i, i);}// 测试 get 操作for (int i = 0; i < testSize; i++) {map.get("key" + i);}long endTime = System.currentTimeMillis();long totalTime = endTime - startTime;System.out.println(mapType + " 性能测试完成,耗时: " + totalTime + "ms");return totalTime;}
}
总结
HashMap 和 Hashtable 关键差异总结
-
Hash 计算方法:
- HashMap:使用扰动函数
(h = key.hashCode()) ^ (h >>> 16)
- Hashtable:使用
hashSeed ^ key.hashCode()
- HashMap:使用扰动函数
-
初始容量:
- HashMap:16(2 的幂次)
- Hashtable:11(任意正整数)
-
扩容机制:
- HashMap:双倍扩容(2n)
- Hashtable:2n+1 扩容
-
线程安全性:
- HashMap:非线程安全
- Hashtable:线程安全(synchronized)
-
null 值处理:
- HashMap:允许 null 键和 null 值
- Hashtable:不允许 null 键和 null 值
-
性能:
- HashMap:性能更好
- Hashtable:性能较差(同步开销)
推荐使用:
- 单线程环境:使用 HashMap
- 多线程环境:使用 ConcurrentHashMap(而不是 Hashtable)
- 不推荐使用 Hashtable(已过时)