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

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


当前回答

HashTable是同步的,如果您在单个线程中使用它,则可以使用HashMap,这是一个未同步的版本。未同步的对象通常性能更高。顺便说一句,如果多个线程同时访问HashMap,并且至少有一个线程在结构上修改了该映射,那么它必须在外部同步。您可以使用以下方法将未同步的映射打包到已同步的映射中:Map m=Collections.synchronizedMap(新HashMap(…));HashTable只能包含非空对象作为键或值。HashMap可以包含一个空键和空值。Map返回的迭代器是快速失败的,如果在创建迭代器之后的任何时候对映射进行了结构修改,则除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException。因此,在并发修改的情况下,迭代器会快速而干净地失败,而不是在未来某个不确定的时间冒着任意、非确定性行为的风险。而Hashtable的键和元素方法返回的枚举不会快速失败。HashTable和HashMap是Java集合框架的成员(自从Java 2平台v1.2以来,HashTable被改进以实现Map接口)。HashTable被认为是遗留代码,如果需要线程安全的高度并发实现,文档建议使用ConcurrentHashMap代替HashTable。HashMap不保证返回元素的顺序。对于HashTable,我想它是相同的,但我不完全确定,我找不到明确说明这一点的ressource。

其他回答

hashtable和hashmap之间的另一个关键区别是,hashmap中的Iterator是快速失败的,而hashtable的枚举器不是快速失败的。如果任何其他线程通过添加或删除Iterator自己的remove()方法以外的任何元素来从结构上修改映射,则抛出ConcurrentModificationException。但这不是一种保证的行为,将由JVM尽最大努力完成。"

我的来源:http://javarevisited.blogspot.com/2010/10/difference-between-hashmap-and.html

HashTable和HashMaps有5个基本区别。

映射允许您迭代和检索键、值以及两个键-值对,而HashTable不具备所有这些功能。Hashtable中有一个函数contains(),使用起来非常混乱。因为contains的意思稍有偏差。它是指包含键还是包含值?很难理解。在Maps中,我们有ContainsKey()和ContainsValue()函数,这两个函数非常容易理解。在hashmap中,您可以在迭代时安全地删除元素。因为这在哈希表中是不可能的。HashTables默认情况下是同步的,因此可以轻松地与多个线程一起使用。默认情况下,as HashMaps不同步,因此只能用于单个线程。但您仍然可以使用Collections-util类的synchronizedMap(Mapm)函数将HashMap转换为synchronized。HashTable不允许空键或空值。其中作为HashMap允许一个空键和多个空值。

根据这里的信息,我建议使用HashMap。我认为最大的优点是,Java将防止您在迭代时修改它,除非您通过迭代器进行修改。

除了这里已经提到的所有其他重要方面之外,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的性能改进