[源碼解析]HashMap和HashTable的區別(源碼分析解讀)


[源碼解析]HashMap和HashTable的區別(源碼分析解讀)

前言: 
又是一個大好的週末, 惋惜今天起來有點晚, 扒開HashMap和HashTable, 看看他們到底有什麼區別吧.

先來一段比較拗口的定義:html

複製代碼

Hashtable 的實例有兩個參數影響其性能:初始容量 和 加載因子。容量 是哈希表中桶 的數量,初始容量 就是哈希表建立時的容量。注意,哈希表的狀態爲 open:在發生「哈希衝突」的狀況下,單個桶會存儲多個條目,這些條目必須按順序搜索。加載因子 是對哈希表在其容量自動增長以前能夠達到多滿的一個尺度。初始容量和加載因子這兩個參數只是對該實現的提示。關於什麼時候以及是否調用 rehash 方法的具體細節則依賴於該實現。

  而HashTable是 基於哈希表的 Map 接口的實現。此實現提供全部可選的映射操做,並容許使用 null 值和 null 鍵。(除了非同步和容許使用 null 以外,HashMap 類與 Hashtable 大體相同。)此類不保證映射的順序,特別是它不保證該順序恆久不變。 此實現假定哈希函數將元素適   當地分佈在各桶之間,可爲基本操做(get 和 put)提供穩定的性能。迭代 collection 視圖所需的時間與 HashMap 實例的「容量」(桶的數量)及其大小(鍵-值映射關係數)成比例。因此,若是迭代性能很重要,則不要將初始容量設置得過高(或將加載因子設置得過低)。
  java

複製代碼

 

一, 實例舉證數組

複製代碼

 1      2     public static void main(String[] args) { 3         Map<String, String> map = new HashMap<String, String>(); 4         map.put("a", "aaa"); 5         map.put("b", "bbb"); 6         map.put("c", "ccc"); 7         map.put("d", "ddd"); 
 8         Iterator<String> iterator = map.keySet().iterator(); 9         while (iterator.hasNext()) {10             Object key = iterator.next();11             System.out.println("map.get(key) is :" + map.get(key));12         }13 14         Hashtable<String, String> tab = new Hashtable<String, String>();15         tab.put("a", "aaa");16         tab.put("b", "bbb");17         tab.put("c", "ccc");18         tab.put("d", "ddd");  
19         Iterator<String> iterator_1 = tab.keySet().iterator();20         while (iterator_1.hasNext()) {21             Object key = iterator_1.next();22             System.out.println("tab.get(key) is :" + tab.get(key));23         }24     }25 }

複製代碼

首先上面有這麼一段代碼, 那麼它的輸出是什麼呢? 
安全

能夠看到, HashMap按照正常順序輸出, 而HashTable輸出的順序卻有些詭異.

2, 源碼分析
看到上面的結果, 那麼咱們就分別來看下HashMap和HashTable的源碼吧.

首先我要來灌輸一些思想, 而後再根據這些定義的規則(前人總結出來的) 再去源碼中一探究竟.

1)HashTable是同步的,HashMap是非同步的
HashTable中put和get方法:
微信

複製代碼

 1 public synchronized V put(K key, V value) { 2         // Make sure the value is not null 3         if (value == null) { 4             throw new NullPointerException(); 5         } 6  7         // Makes sure the key is not already in the hashtable. 8         Entry<?,?> tab[] = table; 9         int hash = key.hashCode();10         int index = (hash & 0x7FFFFFFF) % tab.length;11         @SuppressWarnings("unchecked")12         Entry<K,V> entry = (Entry<K,V>)tab[index];13         for(; entry != null ; entry = entry.next) {14             if ((entry.hash == hash) && entry.key.equals(key)) {15                 V old = entry.value;16                 entry.value = value;17                 return old;18             }19         }20 21         addEntry(hash, key, value, index);22         return null;23     }

複製代碼

 

複製代碼

 1 public synchronized V get(Object key) { 2         Entry<?,?> tab[] = table; 3         int hash = key.hashCode(); 4         int index = (hash & 0x7FFFFFFF) % tab.length; 5         for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { 6             if ((e.hash == hash) && e.key.equals(key)) { 7                 return (V)e.value; 8             } 9         }10         return null;11     }

複製代碼

HashMap中put和get方法:app

1 public V put(K key, V value) {2       return putVal(hash(key), key, value, false, true);3 }
1 public V get(Object key) {2         Node<K,V> e;3         return (e = getNode(hash(key), key)) == null ? null : e.value;4 }

從以上代碼中就能顯而易見的看到HashTable中的put和get方法是被synchronized修飾的, 這種作的區別呢? 
因爲非線程安全,效率上可能高於Hashtable. 若是當多個線程訪問時, 咱們可使用HashTable或者經過Collections.synchronizedMap來同步HashMap。ide


2)HashTable與HashMap實現的接口一致,但HashTable繼承自Dictionary,而HashMap繼承自AbstractMap;
HashTable:
函數

 

 HashMap:
 
源碼分析

 

3)HashTable不容許null值(key和value都不能夠) ,HashMap容許null值(key和value均可以)。post

 在1中咱們能夠看到HashTable若是value爲null就會直接拋出: throw new NullPointerException();
 那麼再看看HashMap put value 具體作了什麼?

複製代碼

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;
}

複製代碼

因而可知, 並無value值進行強制的nullCheck.

4)HashTable有一個contains(Object value)功能和containsValue(Object value)功能同樣。
這裏咱們能夠直接對比HashMap和HashTable有關Contains的方法:

HashTable中的contains方法在HashMap中就被取消了, 那麼咱們來具體看下HashTable中的contains方法的做用: 

複製代碼

 1 public synchronized boolean contains(Object value) { 2         if (value == null) { 3             throw new NullPointerException(); 4         } 5  6         Entry<?,?> tab[] = table; 7         for (int i = tab.length ; i-- > 0 ;) { 8             for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) { 9                 if (e.value.equals(value)) {10                     return true;11                 }12             }13         }14         return false;15 }

複製代碼

而後再看下HashTable中的containsValue方法:

1 public boolean containsValue(Object value) {2         return contains(value);3 }

這裏就很明顯了, contains方法其實作的事情就是containsValue, 裏面將value值使用equals進行對比, 因此在HashTable中直接取消了contains方法而是使用containsValue代替.

5)HashTable使用Enumeration進行遍歷,HashMap使用Iterator進行遍歷。


首先是HashTable中:

 View Code

而後是HashMap中:

 View Code

廢棄的接口:Enumeration
Enumeration接口是JDK1.0時推出的,是最好的迭代輸出接口,最先使用Vector(如今推薦使用ArrayList)時就是使用Enumeration接口進行輸出。雖然Enumeration是一箇舊的類,可是在JDK1.5以後爲Enumeration類進行了擴充,增長了泛型的操做應用。

Enumeration接口經常使用的方法有hasMoreElements()(判斷是否有下一個值)和 nextElement()(取出當前元素),這些方法的功能跟Iterator相似,只是Iterator中存在刪除數據的方法,而此接口不存在刪除操做。

爲何還要繼續使用Enumeration接口
Enumeration和Iterator接口功能類似,並且Iterator的功能還比Enumeration多,那麼爲何還要使用Enumeration?這是由於java的發展經歷了很長時間,一些比較古老的系統或者類庫中的方法還在使用Enumeration接口,所以爲了兼容,仍是須要使用Enumeration。

下面給出HashTable和HashMap的幾種遍歷方式:

 Person.java

 Test.java

6)HashTable中hash數組默認大小是11,增長的方式是 old*2+1。HashMap中hash數組的默認大小是16,並且必定是2的指數。

HashMap:

1 /**2      * The default initial capacity - MUST be a power of two.3      */4     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

HashTable:一般,默認加載因子是 0.75, 這是在時間和空間成本上尋求一種折衷。加載因子太高雖然減小了空間開銷,但同時也增長了查找某個條目的時間(在大多數 Hashtable 操做中,包括 get 和 put 操做,都反映了這一點)。

1  // 默認構造函數。2 public Hashtable() {3     // 默認構造函數,指定的容量大小是11;加載因子是0.754     this(11, 0.75f);5 }

 

7)哈希值的使用不一樣
HashTable:,HashTable直接使用對象的hashCode

1 int hash = key.hashCode();2 int index = (hash & 0x7FFFFFFF) % tab.length;

HashMap:HashMap從新計算hash值,並且用與代替求模:

複製代碼

1 int hash = hash(k);2 int i = indexFor(hash, table.length);3 static int hash(Object x) {4 h ^= (h >>> 20) ^ (h >>> 12);5      return h ^ (h >>> 7) ^ (h >>> 4);6 }7 static int indexFor(int h, int length) {8 return h & (length-1);9 }

複製代碼

 

3,其餘關聯
3.1HashMap與HashSet的關係

a、HashSet底層是採用HashMap實現的:

1 public HashSet() {2     map = new HashMap<E,Object>();3 }

b、調用HashSet的add方法時,其實是向HashMap中增長了一行(key-value對),該行的key就是向HashSet增長的那個對象,該行的value就是一個Object類型的常量。

1 private static final Object PRESENT = new Object(); public boolean add(E e) { 
2     return map.put(e, PRESENT)==null; 
3 } 
4 public boolean remove(Object o) { 
5     return map.remove(o)==PRESENT; 
6 }

3.2 HashMap 和 ConcurrentHashMap 的關係

關於這部份內容建議本身去翻翻源碼,ConcurrentHashMap 也是一種線程安全的集合類,他和HashTable也是有區別的,主要區別就是加鎖的粒度以及如何加鎖,ConcurrentHashMap 的加鎖粒度要比HashTable更細一點。將數據分紅一段一段的存儲,而後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其餘段的數據也能被其餘線程訪問。

更多請參考: http://www.hollischuang.com/archives/82


4. HashTable源碼奉上

 

 View Code

 

 

 

分類: 源碼閱讀

好文要頂 關注我 收藏該文  

一枝花算不算浪漫

Powered by .NET 5.0.0-rc.2.20475.5 on Kubernetes

相關文章
相關標籤/搜索