Java中HashMap和Hashtable之间的区别是什么?

对于非线程应用程序,哪个更有效?


当前回答

对于线程应用程序,您通常可以使用ConcurrentHashMap,这取决于您的性能要求。

其他回答

HashMap:使用哈希代码对数组进行索引的Map接口的实现。哈希表:嗨,1998年来电话。他们想拿回他们的集合API。

说真的,你最好完全远离Hashtable。对于单线程应用程序,您不需要额外的同步开销。对于高度并发的应用程序,偏执的同步可能会导致饥饿、死锁或不必要的垃圾收集暂停。正如Tim Howland所指出的,您可以使用ConcurrentHashMap。

对于线程应用程序,您通常可以使用ConcurrentHashMap,这取决于您的性能要求。

除了这里已经提到的所有其他重要方面之外,CollectionsAPI(例如Map接口)一直在进行修改,以符合Java规范的“最新和最大”添加。

例如,比较Java 5 Map迭代:

for (Elem elem : map.keys()) {
  elem.doSth();
}

与旧的Hashtable方法相比:

for (Enumeration en = htable.keys(); en.hasMoreElements(); ) {
  Elem elem = (Elem) en.nextElement();
  elem.doSth();
}

在Java 1.8中,我们还承诺能够像使用好的旧脚本语言一样构造和访问HashMaps:

Map<String,Integer> map = { "orange" : 12, "apples" : 15 };
map["apples"];

更新:不,他们不会在1.8…:(

Project Coin的收藏增强功能是否将在JDK8中?

集合(有时称为容器)只是将多个元素分组为单个单元的对象。集合用于存储、检索、操作和传递聚合数据。集合框架W是用于表示和操作集合的统一架构。

HashMap JDK1.2和Hashtable JDK1.0都用于表示一组对象,这些对象在<Key,Value>对中表示。每个<Key,Value>对都称为Entry对象。条目集合由HashMap和Hashtable的对象引用。集合中的键必须是唯一的或与众不同的。[因为它们用于检索特定键的映射值。集合中的值可以重复。]


«超级类、传统和收藏框架成员

Hashtable是JDK1.0中引入的一个遗留类,它是Dictionary类的一个子类。从JDK1.2哈希表被重新设计以实现Map接口,从而成为集合框架的成员。HashMap从JDK1.2引入之初就是Java集合框架的成员。HashMap是AbstractMap类的子类。

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, Serializable { ... }

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { ... }

«初始容量和负载系数

容量是哈希表中桶的数量,初始容量只是创建哈希表时的容量。注意,哈希表是开放的:在“哈希冲突”的情况下,一个存储桶存储多个条目,这些条目必须按顺序搜索。负载因子是在哈希表的容量自动增加之前,允许哈希表达到多满的度量。

HashMap使用默认初始容量(16)和默认负载因子(0.75)构造一个空哈希表。Where as Hashtable使用默认初始能力(11)和负载因子/填充比(0.75)构建空哈希表。

«哈希冲突时的结构修改

HashMap,Hashtable在哈希冲突的情况下,它们将映射条目存储在链接列表中。从Java8 for HashMap中,如果哈希桶增长超过某个阈值,该桶将从条目链接列表切换到平衡树。这将最坏情况性能从O(n)提高到O(logn)。在将列表转换为二进制树时,哈希代码用作分支变量。如果同一个存储桶中有两个不同的哈希码,一个被认为更大,并位于树的右侧,另一个位于左侧。但是,当两个哈希码相等时,HashMap假设键是可比较的,并比较键以确定方向,以便保持某种顺序。使HashMap的键具有可比性是一个很好的做法。在添加条目时,如果存储桶大小达到TREEIFY_THRESHOLD=8,则将条目的链接列表转换为平衡树;在删除小于TREEIFY_THRESHOLD且最多为UNTREEIFY_THRESHOLD=6的条目时,将重新将平衡树转换为条目的链接表。Java 8 SRC,堆栈

«集合视图迭代、快速故障和安全故障

    +--------------------+-----------+-------------+
    |                    | Iterator  | Enumeration |
    +--------------------+-----------+-------------+
    | Hashtable          | fail-fast |    safe     |
    +--------------------+-----------+-------------+
    | HashMap            | fail-fast | fail-fast   |
    +--------------------+-----------+-------------+
    | ConcurrentHashMap  |   safe    |   safe      |
    +--------------------+-----------+-------------+

迭代器本质上是一种快速故障。即,如果在迭代而不是自己的remove()方法时修改了集合,则会抛出ConcurrentModificationException。其中作为枚举本质上是故障安全的。如果在迭代时修改了集合,则不会引发任何异常。

根据JavaAPI文档,迭代器总是优先于枚举。

注意:枚举接口的功能与迭代器接口重复。此外,Iterator添加了一个可选的移除操作,并且方法名称更短。新的实现应该考虑使用迭代器而不是枚举。

在Java5中引入了ConcurrentMap接口:ConcurrentHashMap——一个由哈希表支持的高度并发、高性能的ConcurrentMap实现。此实现在执行检索时从不阻塞,并允许客户端选择更新的并发级别。它旨在作为Hashtable的替代品:除了实现ConcurrentMap之外,它还支持Hashtable特有的所有“遗留”方法。

每个HashMapEntrys值都是可变的,从而确保争用修改和后续读取的细粒度一致性;每次读取都反映最近完成的更新迭代器和枚举是故障安全的-反映自创建迭代器/枚举以来的某个点的状态;这允许以降低一致性为代价同时进行读取和修改。它们不会引发ConcurrentModificationException。然而,迭代器被设计为一次只能由一个线程使用。与Hashtable类似,但与HashMap不同,此类不允许将null用作键或值。

public static void main(String[] args) {

    //HashMap<String, Integer> hash = new HashMap<String, Integer>();
    Hashtable<String, Integer> hash = new Hashtable<String, Integer>();
    //ConcurrentHashMap<String, Integer> hash = new ConcurrentHashMap<>();
    
    new Thread() {
        @Override public void run() {
            try {
                for (int i = 10; i < 20; i++) {
                    sleepThread(1);
                    System.out.println("T1 :- Key"+i);
                    hash.put("Key"+i, i);
                }
                System.out.println( System.identityHashCode( hash ) );
            } catch ( Exception e ) {
                e.printStackTrace();
            }
        }
    }.start();
    new Thread() {
        @Override public void run() {
            try {
                sleepThread(5);
                // ConcurrentHashMap  traverse using Iterator, Enumeration is Fail-Safe.
                
                // Hashtable traverse using Enumeration is Fail-Safe, Iterator is Fail-Fast.
                for (Enumeration<String> e = hash.keys(); e.hasMoreElements(); ) {
                    sleepThread(1);
                    System.out.println("T2 : "+ e.nextElement());
                }
                
                // HashMap traverse using Iterator, Enumeration is Fail-Fast.
                /*
                for (Iterator< Entry<String, Integer> > it = hash.entrySet().iterator(); it.hasNext(); ) {
                    sleepThread(1);
                    System.out.println("T2 : "+ it.next());
                    // ConcurrentModificationException at java.util.Hashtable$Enumerator.next
                }
                */
                
                /*
                Set< Entry<String, Integer> > entrySet = hash.entrySet();
                Iterator< Entry<String, Integer> > it = entrySet.iterator();
                Enumeration<Entry<String, Integer>> entryEnumeration = Collections.enumeration( entrySet );
                while( entryEnumeration.hasMoreElements() ) {
                    sleepThread(1);
                    Entry<String, Integer> nextElement = entryEnumeration.nextElement();
                    System.out.println("T2 : "+ nextElement.getKey() +" : "+ nextElement.getValue() );
                    //java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextNode
                    //                                          at java.util.HashMap$EntryIterator.next
                    //                                          at java.util.Collections$3.nextElement
                }
                */
            } catch ( Exception e ) {
                e.printStackTrace();
            }
        }
    }.start();
    
    Map<String, String> unmodifiableMap = Collections.unmodifiableMap( map );
    try {
        unmodifiableMap.put("key4", "unmodifiableMap");
    } catch (java.lang.UnsupportedOperationException e) {
        System.err.println("UnsupportedOperationException : "+ e.getMessage() );
    }
}
static void sleepThread( int sec ) {
    try {
        Thread.sleep( 1000 * sec );
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

«空键和空值

HashMap最多允许一个空键和任意数量的空值。如果as Hashtable甚至不允许单个null键和null值,那么如果键或值为null,则会引发NullPointerException。实例

«同步,线程安全

哈希表是内部同步的。因此,在多线程应用程序中使用Hashtable非常安全。其中as HashMap未在内部同步。因此,在没有外部同步的多线程应用程序中使用HashMap是不安全的。可以使用Collections.synchronizedMap()方法从外部同步HashMap。

«性能

由于Hashtable是内部同步的,这使得Hashtable比HashMap稍慢。


@See

红黑树是一种自平衡的二叉搜索树Java8中HashMap的性能改进

HashMap是模拟的,因此可以在GWT客户端代码中使用,而Hashtable不是。