Hashtable介绍:
该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键值或值。
为了从散列表成功存储和检索对象,用作键的对象必须实现hashCode方法和equals方法。
Hashtable一个实例有两个影响其性能的参数: 初始容量和负载因子 。 容量是哈希表中的桶数, 初始容量只是创建哈希表时的容量。 请注意,哈希表是打开的 :在“哈希冲突”的情况下,单个存储桶存储多个条目,必须依次搜索。 负载因子是在容量自动增加之前允许哈希表得到满足的度量。 初始容量和负载因子参数仅仅是实现的暗示。 关于何时以及是否调用rehash方法的具体细节是依赖于实现的。
通常,默认负载因子(.75)提供了时间和空间成本之间的良好折衷。 更高的值会减少空间开销,但会增加查询条目的时间成本(这反映在大多数Hashtable操作中,包括get和put )。
初始容量控制了浪费空间与需要rehash操作之间的折中,这是耗时的。 没有rehash如果初始容量大于项Hashtable将其负载因子包含除以最大数量永远不会发生的操作。 然而,设置初始容量太高可能会浪费空间。
如果将多个条目制作为Hashtable ,则以足够大的容量创建它可能会使条目更有效地插入,以使其根据需要执行自动重新排序以增长表。
HashMap介绍:
基于哈希表的实现的Map接口。 此实现提供了所有可选的地图操作,并允许null的值和null键。 ( HashMap类大致相当于Hashtable ,除了它是不同步的,并允许null)。这个类不能保证地图的顺序; 特别是,它不能保证订单在一段时间内保持不变。
假设哈希函数在这些存储桶之间正确分散元素,这个实现为基本操作( get和put )提供了恒定的时间性能。 收集视图的迭代需要与HashMap实例(桶数)加上其大小(键值映射数)的“容量” 成正比 。 因此,如果迭代性能很重要,不要将初始容量设置得太高(或负载因子太低)是非常重要的。
HashMap的一个实例有两个影响其性能的参数: 初始容量和负载因子 。 容量是哈希表中的桶数,初始容量只是创建哈希表时的容量。 负载因子是在容量自动增加之前允许哈希表得到满足的度量。 当在散列表中的条目的数量超过了负载因数和电流容量的乘积,哈希表被重新散列 (即,内部数据结构被重建),使得哈希表具有桶的大约两倍。
作为一般规则,默认负载因子(.75)提供了时间和空间成本之间的良好折中。 更高的值会降低空间开销,但会增加查找成本(反映在HashMap类的大部分操作中,包括get和put )。 在设置其初始容量时,应考虑地图中预期的条目数及其负载因子,以便最小化重新组播操作的数量。 如果初始容量大于最大条目数除以负载因子,则不会发生重新排列操作。
如果许多映射要存储在HashMap实例中,则以足够大的容量创建映射将允许映射的存储效率高于使其根据需要执行自动重新排序以增长表。 请注意,使用同一个hashCode()多个密钥是降低任何哈希表的hashCode()的一种方法。 为了改善影响,当按键是Comparable时,这个类可以使用键之间的比较顺序来帮助打破关系。
请注意,此实现不同步。 如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了映射,那么它必须在外部进行同步。 (结构修改是添加或删除一个或多个映射的任何操作;仅改变与实例已经包含的密钥相关联的值不是结构修改。)这通常通过对自然地封装映射的一些对象进行同步来实现。 如果没有这样的对象存在,地图应该使用Collections.synchronizedMap方法“包装”。
介绍来自机翻的JDK注释
区别总结
作者可以发现的区别:
-
HashMap是线程不安全的;
源码:
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) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
HashMap的put()方式是没有同步或者其它锁的,核心的putVal()也是没有同步或者锁的,putVal()方法所调用的插入方法更是没有同步或者锁,故HashMap确实不是线程安全的。
-
Hashtable是线程安全的;
源码:
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; }
从源码中可以看到,Hashtable的put方法是通过synchronized修饰的,至少可以证明put()是同步的,所以是线程安全的。
-
HashMap是可以存储键和值均为null的元素;
源码:
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; }
可以从源码中看到,HashMap对传入的Key和Value并没有进行Null值判断。
当key值为Null时,Hash值为0;
源码:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
-
Hashtable是不可以存储键和值为null的元素;
源码:
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; }
可以从源码里看出Hashtable对传入的Value做出了Null判断。
而从“int hash = key.hashCode();”得出Key也不可为空。