当前位置:   article > 正文

Java HashMap 和 Hashtable 的区别?

Java HashMap 和 Hashtable 的区别?

Java 编程中,HashMap 和 Hashtable 是两个常用的哈希表实现,但它们在多个方面存在显著区别。了解这些区别对于编写高效且线程安全的代码至关重要。我们将通过源码分析来详细探讨这些差异。

线程是否安全

HashMap 是非线程安全的,而 Hashtable 是线程安全的。这是由于 Hashtable 内部的许多方法都使用了 synchronized 关键字进行修饰,确保了线程安全性。

Hashtable 的线程安全实现

以下是 Hashtable 中 put 方法的简化代码:

java

  1. public synchronized V put(K key, V value) {
  2. // Null key or value is not allowed
  3. if (key == null || value == null) {
  4. throw new NullPointerException();
  5. }
  6. // Hashing and insertion logic
  7. }
HashMap 的非线程安全实现

相对应的,HashMap 的 put 方法没有使用 synchronized 关键字:

java

  1. public V put(K key, V value) {
  2. return putVal(hash(key), key, value, false, true);
  3. }
  4. final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
  5. // Hashing and insertion logic
  6. }

效率

由于线程安全机制的开销,Hashtable 的效率通常低于 HashMap。在多线程环境中,如果需要线程安全的哈希表,可以使用 ConcurrentHashMap,其设计更加高效。

ConcurrentHashMap 的实现

ConcurrentHashMap 通过分段锁(Segmented Locking)提高并发性能:

java

  1. public V put(K key, V value) {
  2. return putVal(key, value, false);
  3. }
  4. private V putVal(K key, V value, boolean onlyIfAbsent) {
  5. // Segment locking and insertion logic
  6. }

对 Null key 和 Null value 的支持

HashMap 允许一个 null 键和多个 null 值,而 Hashtable 不允许 null 键和 null 值。

HashMap 的实现示例

java

  1. public V put(K key, V value) {
  2. if (key == null) {
  3. return putForNullKey(value);
  4. }
  5. // Normal insertion logic
  6. }
  7. private V putForNullKey(V value) {
  8. for (Node<K,V> e = table[0]; e != null; e = e.next) {
  9. if (e.key == null) {
  10. V oldValue = e.value;
  11. e.value = value;
  12. return oldValue;
  13. }
  14. }
  15. table[0] = new Node<>(0, null, value, table[0]);
  16. return null;
  17. }
Hashtable 的实现示例

java

  1. public synchronized V put(K key, V value) {
  2. if (key == null || value == null) {
  3. throw new NullPointerException();
  4. }
  5. // Normal insertion logic
  6. }

初始容量和扩容机制

HashMap 默认初始容量为 16,扩容时容量加倍。而 Hashtable 默认初始容量为 11,扩容时容量变为原来的 2 倍加 1。

HashMap 的容量计算

HashMap 使用 tableSizeFor 方法确保容量为 2 的幂次方:

java

  1. static final int tableSizeFor(int cap) {
  2. int n = cap - 1;
  3. n |= n >>> 1;
  4. n |= n >>> 2;
  5. n |= n >>> 4;
  6. n |= n >>> 8;
  7. n |= n >>> 16;
  8. return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  9. }
Hashtable 的扩容机制

java

  1. protected void rehash() {
  2. int oldCapacity = table.length;
  3. Entry<?,?>[] oldMap = table;
  4. int newCapacity = (oldCapacity << 1) + 1;
  5. Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
  6. // Rehashing logic
  7. }

底层数据结构

在 JDK 1.8 及以后的版本中,HashMap 在处理哈希冲突时,当链表长度超过阈值(默认为 8)时会将链表转换为红黑树。这大大提高了在发生哈希冲突时的查询效率。而 Hashtable 没有这种优化机制。

HashMap 红黑树转换逻辑

java

  1. final void treeifyBin(Node<K,V>[] tab, int hash) {
  2. int n, index; Node<K,V> e;
  3. if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
  4. resize();
  5. else if ((e = tab[index = (n - 1) & hash]) != null) {
  6. TreeNode<K,V> hd = null, tl = null;
  7. do {
  8. TreeNode<K,V> p = replacementTreeNode(e, null);
  9. if (tl == null)
  10. hd = p;
  11. else {
  12. p.prev = tl;
  13. tl.next = p;
  14. }
  15. tl = p;
  16. } while ((e = e.next) != null);
  17. if ((tab[index] = hd) != null)
  18. hd.treeify(tab);
  19. }
  20. }

总结

HashMap 和 Hashtable 各有优缺点。了解它们的实现细节可以帮助我们在实际应用中做出更明智的选择。通常,在单线程或并发读多于写的场景中使用 HashMap,在多线程写操作频繁的场景中使用 ConcurrentHashMap 更为合适。Hashtable 因为其性能和设计上的限制,已逐渐被淘汰,不推荐在新代码中使用。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/996183
推荐阅读
相关标签
  

闽ICP备14008679号