由于HashMap与HashTable都是用来存储Key-Value的键值对,所以经常拿来对比二者的区别,下面就从源码的角度来分析一下HashMap与HashTable的区别,
首先介绍一下两者的区别,然后再从源码分析。
HahMap与HahTable两者主要区别:
1、继承的父类不同
<span style="font-size:18px;">public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Serializable</span>
<span style="font-size:18px;"><pre name="code" class="java">public class Hashtable<K, V> extends Dictionary<K, V> implements Map<K, V>, Cloneable, Serializable</span>
HashTable是一个古老的Map实现类,从JDK1.0就开始出现,继承自Dictionary,当时JDK还有没有出现Map这个接口;HashMap继承自AbstractMap这个抽象类,而AbstractMap这个抽象类实现了Map接口。
2、HashTable中的方法是同步的,在多线程程序中无需程序员自己去实现同步处理;而HashMap中的方法是非同步的,需要程序员自己去实现同步处理,常用的方法是使用Collections工具类中的synchronizedXxx()方法吧集合类包装成线程同步的集合。
3、HashTable中Key与Value都不允许出现NULL值,而HashMap中Key可以为NULL,由于HashMap是Map中实现类,Map集合中要求每个Key唯一,所以HashMap中之多只能出现一个Key为NULL的情况,但HashMap中可以出现多个Value为NULL的情况。
4、遍历方式不同
Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式
。
然后接下来看看HashMap的源码。
HashMap中一些属性Field如下:
<span style="font-size:18px;"> /** * Min capacity (other than zero) for a HashMap. Must be a power of two * greater than 1 (and less than 1 << 30). * HashMap的最小容量 */ private static final int MINIMUM_CAPACITY = 4; /** * Max capacity for a HashMap. Must be a power of two >= MINIMUM_CAPACITY. * HashMap的最大容量 */ private static final int MAXIMUM_CAPACITY = 1 << 30; /** * An empty table shared by all zero-capacity maps (typically from default * constructor). It is never written to, and replaced on first put. Its size * is set to half the minimum, so that the first resize will create a * minimum-sized table. * * HashMapEntry数组,HashMapEntry条目数组,HashMapEntry数组大小为MINIMUM_CAPACITY/2 */ private static final Entry[] EMPTY_TABLE = new HashMapEntry[MINIMUM_CAPACITY >>> 1]; /** * The default load factor. Note that this implementation ignores the * load factor, but cannot do away with it entirely because it's * mentioned in the API. * * <p>Note that this constant has no impact on the behavior of the program, * but it is emitted as part of the serialized form. The load factor of * .75 is hardwired into the program, which uses cheap shifts in place of * expensive division. * 默认的负载因子 */ static final float DEFAULT_LOAD_FACTOR = .75F; /** * The hash table. If this hash map contains a mapping for null, it is * not represented this hash table. * HashMap容器数组,用于存放HashMapEntry对象的数组 */ transient HashMapEntry<K, V>[] table; /** * The entry representing the null key, or null if there's no such mapping. * 空键值对应的HashMapEntry条目对象 */ transient HashMapEntry<K, V> entryForNullKey; /** * The number of mappings in this hash map. * HashMap中元素的数量(大小) */ transient int size; /** * Incremented by "structural modifications" to allow (best effort) * detection of concurrent modification. * 用于确保使用迭代器的时候,HashMap并未进行更改 */ transient int modCount; /** * The table is rehashed when its size exceeds this threshold. * The value of this field is generally .75 * capacity, except when * the capacity is zero, as described in the EMPTY_TABLE declaration * above. * 负载因子(阈值) */ private transient int threshold; // Views - lazily initialized /**由K键组成的Set集合*/ private transient Set<K> keySet; /**由K-Value组成的Set集合,Set集合中存放了Entry<K, V>元素*/ private transient Set<Entry<K, V>> entrySet; private transient Collection<V> values;</span>
HashMap中是用HashMapEntry数组来存储Key-Value键值对的,HashMapEntry是HashMap中一个静态的内部类,实现了java.util.Map.Entry接口。在HashMap的属性Field中定义用于存放Key-Value键值对的HashMapEntry数组长度的默认的最小值和最大值。一个空的HashMapEntry数组EMPTY_TABLE以及HashMap容器数组tab,用于存放HashMapEntry对象的数组。默认的负载因子DEFAULT_LOAD_FACTOR,将其定义为0.75(3/4)。
HashMap的构造函数主要有以下几个:
HashMap()
Constructs a new empty |
HashMap(int capacity)
Constructs a new |
HashMap(int capacity, float loadFactor)
Constructs a new |
HashMap(Map<? extendsK,?
Constructs a new |
我们来看下HashMap(int capacity)这个构造函数的源码
<span style="font-size:18px;"> /** * Constructs a new {@code HashMap} instance with the specified capacity. * * @param capacity * the initial capacity of this hash map. * @throws IllegalArgumentException * when the capacity is less than zero. *通过一个指定的容量来构造一个HashMap实例对象 *@param capacity:HashMap初始化容量 * */ public HashMap(int capacity) { if (capacity < 0) { throw new IllegalArgumentException("Capacity: " + capacity); } if (capacity == 0) { @SuppressWarnings("unchecked") HashMapEntry<K, V>[] tab = (HashMapEntry<K, V>[]) EMPTY_TABLE; table = tab; threshold = -1; // Forces first put() to replace EMPTY_TABLE return; } if (capacity < MINIMUM_CAPACITY) { capacity = MINIMUM_CAPACITY; } else if (capacity > MAXIMUM_CAPACITY) { capacity = MAXIMUM_CAPACITY; } else { capacity = Collections.roundUpToPowerOfTwo(capacity); } makeTable(capacity); }</span>
该函数通过一个指定的容量值capacity来创建一个HashMapEntry数组对象,当capacity等于0时将空的HashMapEntry数组对象EMPTY_TABLE赋值给tab对象;如果capacity不为0时,当capacity小于容量最小值时将capacity调整为容量最小值,同理当capacity大于容量最大值时将capacity调整为容量最大值。最后调用makeTable来初始化创建tab数组。下面是makeTable的具体实现
<span style="font-size:18px;"> /** * Allocate a table of the given capacity and set the threshold accordingly. * 根据指定容量来分配table数组的大小,并设置负载因子(阈值) * @param newCapacity must be a power of two * */ private HashMapEntry<K, V>[] makeTable(int newCapacity) { @SuppressWarnings("unchecked") HashMapEntry<K, V>[] newTable = (HashMapEntry<K, V>[]) new HashMapEntry[newCapacity]; table = newTable; threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity return newTable; }</span>
可以发现在makeTable中是通过创建一个按指定容量大小新的HashMapEntry数组newTable,并将其newTable数组赋值给HashMap的属性File变量table,因此在HashMap中table数组才是真正的存放HashMapEntry的容器,即存放Key-Value条目的容器。在构造函数中将负载因子threshold设置为容量的3/4,当table数组中存放的HashMapEntry元素个数大于负载因子threshold时table数组将扩容。通过查看java.util.HashMap.put(K
key, V value)源码即可发现这点:
<span style="font-size:18px;"> /** * Maps the specified key to the specified value. * * @param key * the key. * @param value * the value. * @return the value of any previous mapping with the specified key or * {@code null} if there was no such mapping. */ @Override public V put(K key, V value) { if (key == null) { return putValueForNullKey(value); } int hash = secondaryHash(key); HashMapEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) { if (e.hash == hash && key.equals(e.key)) { preModify(e); V oldValue = e.value; e.value = value; return oldValue; } } // No entry for (non-null) key is present; create one modCount++; if (size++ > threshold) { tab = doubleCapacity(); index = hash & (tab.length - 1); } addNewEntry(key, value, hash, index); return null; }</span>
HashMap.put方法用于向HashMap中插入HashMapEntry元素(即插入Key-Value键值对),当key为空的时候调用putValueForNullKey方法将value加入到key为NULL的HashMapEntry对象中。然后再检测当前传入的key是否在table数组中已经存在,如果存在就将新的value值替换掉key对应旧的value值oldValue并将oldValue返回;如果key在table数组中不存在则调用addNewEntry(K
key, V value, int hash, int index) 方法将Value对象添加到table数组中。不过在加入到table数组之前要先进行判断,如果table数组元素数量size大于负载因子threshold则调用doubleCapacity来给table数组扩容。doubleCapacity方法源码如下:
<span style="font-size:18px;"> /** * Doubles the capacity of the hash table. Existing entries are placed in * the correct bucket on the enlarged table. If the current capacity is, * MAXIMUM_CAPACITY, this method is a no-op. Returns the table, which * will be new unless we were already at MAXIMUM_CAPACITY. * 将数组容量扩大 */ private HashMapEntry<K, V>[] doubleCapacity() { /**旧的table数组*/ HashMapEntry<K, V>[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { return oldTable; } int newCapacity = oldCapacity * 2; HashMapEntry<K, V>[] newTable = makeTable(newCapacity); if (size == 0) { return newTable; } for (int j = 0; j < oldCapacity; j++) { /* * Rehash the bucket using the minimum number of field writes. * This is the most subtle and delicate code in the class. */ HashMapEntry<K, V> e = oldTable[j]; if (e == null) { continue; } int highBit = e.hash & oldCapacity; HashMapEntry<K, V> broken = null; newTable[j | highBit] = e; for (HashMapEntry<K, V> n = e.next; n != null; e = n, n = n.next) { int nextHighBit = n.hash & oldCapacity; if (nextHighBit != highBit) { if (broken == null) newTable[j | nextHighBit] = n; else broken.next = n; broken = e; highBit = nextHighBit; } } if (broken != null) broken.next = null; } return newTable; }</span>
下面再看看java.util.HashMap.get(Object key)方法源码:
<span style="font-size:18px;"> /** * Returns the value of the mapping with the specified key. * * @param key * the key. * @return the value of the mapping with the specified key, or {@code null} * if no mapping for the specified key is found. */ public V get(Object key) { if (key == null) { HashMapEntry<K, V> e = entryForNullKey; return e == null ? null : e.value; } // Doug Lea's supplemental secondaryHash function (inlined). // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590). int hash = key.hashCode(); hash ^= (hash >>> 20) ^ (hash >>> 12); hash ^= (hash >>> 7) ^ (hash >>> 4); HashMapEntry<K, V>[] tab = table; for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)]; e != null; e = e.next) { K eKey = e.key; if (eKey == key || (e.hash == hash && key.equals(eKey))) { return e.value; } } return null; }</span>
在HashMap.get方法可以发现HashMap在查找Key的过程中,如果是自定义类作为Key对象的话,通常要求重写equals方法和hashCode方法,并且equals方法和hashCode方法判断标准必须保持一致,否则将出现冲突。即在使用自定义类作为HashMap中Key对象,HashMap判断两个Key相等的标准是:两个Key通过equals()方法比较返回true,两个Key的hashCode值也必须相等。
与此同时,我们再看看HashTable的源码,可以发现HashTable与HashMap有一部分方式的底层实现差不多,在此就不详述,主要看下两者在底层实现中的一些区别。
java.util.Hashtable.put(K key, V value)源码:
<span style="font-size:18px;"> /** * Associate the specified value with the specified key in this * {@code Hashtable}. If the key already exists, the old value is replaced. * The key and value cannot be null. * * @param key * the key to add. * @param value * the value to add. * @return the old value associated with the specified key, or {@code null} * if the key did not exist. * @see #elements * @see #get * @see #keys * @see java.lang.Object#equals */ public synchronized V put(K key, V value) { if (key == null) { throw new NullPointerException("key == null"); } else if (value == null) { throw new NullPointerException("value == null"); } int hash = Collections.secondaryHash(key); HashtableEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); HashtableEntry<K, V> first = tab[index]; for (HashtableEntry<K, V> e = first; e != null; e = e.next) { if (e.hash == hash && key.equals(e.key)) { V oldValue = e.value; e.value = value; return oldValue; } } // No entry for key is present; create one modCount++; if (size++ > threshold) { rehash(); // Does nothing!! tab = doubleCapacity(); index = hash & (tab.length - 1); first = tab[index]; } tab[index] = new HashtableEntry<K, V>(key, value, hash, first); return null; }</span>
HashTable.put方法与HashMap.put方法基本实现逻辑相同,主要区别是HashTable.put方法被synchronized关键字修饰,因此HashTable是线程安全的。同时如果Key为NULL的话,在HashTable.put方法中将会报异常,所以在HashTable中Key不能为NULL,而我们在前面的分析中可以发现HashMap.put方法中Key是可以为NULL的。
下面给出java.util.Hashtable.get(Object key)源码,亦可以发现相似结果,故就不具体分析。
<span style="font-size:18px;"> /** * Returns the value associated with the specified key in this * {@code Hashtable}. * * @param key * the key of the value returned. * @return the value associated with the specified key, or {@code null} if * the specified key does not exist. * @see #put */ public synchronized V get(Object key) { int hash = Collections.secondaryHash(key); HashtableEntry<K, V>[] tab = table; for (HashtableEntry<K, V> e = tab[hash & (tab.length - 1)]; e != null; e = e.next) { K eKey = e.key; if (eKey == key || (e.hash == hash && key.equals(eKey))) { return e.value; } } return null; }</span>
HashMap源码(JDK1.7,含注释) HashTable源码(JDK1.7,含注释)