1. 程式人生 > >HashMap和HashTable到底哪不同?

HashMap和HashTable到底哪不同?



HashMap和HashTable有什麼不同?在面試和被面試的過程中,我問過也被問過這個問題,也見過了不少回答,今天決定寫一寫自己心目中的理想答案。

JDK每一版本都在改進。本文討論的HashMap和HashTable基於JDK 1.7.0_67。

1. 時間

HashTable產生於JDK 1.1,而HashMap產生於JDK 1.2。從時間的維度上來看,HashMap要比HashTable出現得晚一些。

2. 作者

以下是HashTable的作者:

以下程式碼及註釋來自java.util.HashTable

* @author  Arthur van Hoff
* @author  Josh Bloch
* @author  Neal Gafter

以下是HashMap的作者:

以下程式碼及註釋來自java.util.HashMap

* @author  Doug Lea
* @author  Josh Bloch
* @author  Arthur van Hoff
* @author  Neal Gafter

可以看到HashMap的作者多了大神Doug Lea。不瞭解Doug Lea的,可以看https://en.wikipedia.org/wiki/Doug_Lea。

3. 對外的介面(API)

HashMap和HashTable都是基於雜湊表來實現鍵值對映的工具類。討論他們的不同,我們首先來看一下他們暴露在外的API有什麼不同。

13.1 Public Method

下面兩張圖,我畫出了HashMap和HashTable的類繼承體系,並列出了這兩個類的可供外部呼叫的公開方法。

從圖中可以看出,兩個類的繼承體系有些不同。雖然都實現了Map、Cloneable、Serializable三個介面。但是HashMap繼承自抽象類AbstractMap,而HashTable繼承自抽象類Dictionary。

其中Dictionary類是一個已經被廢棄的類,這一點我們可以從它程式碼的註釋中看到:

以下程式碼及註釋來自java.util.Dictionary

* <strong>NOTE: This class
 is obsolete.  New implementations should

* implement the Map interface, rather than extending this class.</strong>

同時我們看到HashTable比HashMap多了兩個公開方法。一個是elements,這來自於抽象類Dictionary,鑑於該類已經廢棄,所以這個方法也就沒什麼用處了。另一個多出來的方法是contains,這個多出來的方法也沒什麼用,因為它跟containsValue方法功能是一樣的。程式碼為證:

以下程式碼及註釋來自java.util.HashTable

public synchronized boolean contains(Object value
{
   if (value == null) {
       throw new NullPointerException();
   }

   Entry tab[] = table;
   for (int i = tab.length ; i-- > 0 ;) {
       for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {
           if (e.value.equals(value)) {
               return true;
           }
       }
   }
   return false;
}

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

所以從公開的方法上來看,這兩個類提供的,是一樣的功能。都提供鍵值對映的服務,可以增、刪、查、改鍵值對,可以對建、值、鍵值對提供遍歷檢視。支援淺拷貝,支援序列化。

13.2 Null Key & Null Value

HashMap是支援null鍵和null值的,而HashTable在遇到null時,會丟擲NullPointerException異常。這並不是因為HashTable有什麼特殊的實現層面的原因導致不能支援null鍵和null值,這僅僅是因為HashMap在實現時對null做了特殊處理,將null的hashCode值定為了0,從而將其存放在雜湊表的第0個bucket中。我們一put方法為例,看一看程式碼的細節:

以下程式碼及註釋來自java.util.HashTable

public synchronized V put(K key, V value{

  // 如果value為null,丟擲NullPointerException
  if (value == null) {
      throw new NullPointerException();
  }

  // 如果key為null,在呼叫key.hashCode()時丟擲NullPointerException

  // ...
}

以下程式碼及註釋來自java.util.HasMap

public V put(K key, V value{
  if (table == EMPTY_TABLE) {
      inflateTable(threshold);
  }
  // 當key為null時,呼叫putForNullKey特殊處理
  if (key == null)
      return putForNullKey(value);
  // ...
}

private V putForNullKey(value{
  // key為null時,放到table[0]也就是第0個bucket中
  for (Entry<K,V> e = table[0]; e != null; e = e.next) {
      if (e.key == null) {
          V oldValue = e.value;
          e.value = value;
          e.recordAccess(this);
          return oldValue;
      }
  }
  modCount++;
  addEntry(0nullvalue0);
  return null;
}

4.實現原理

本節討論HashMap和HashTable在資料結構和演算法層面,有什麼不同。

14.1 資料結構

HashMap和HashTable都使用雜湊表來儲存鍵值對。在資料結構上是基本相同的,都建立了一個繼承自Map.Entry的私有的內部類Entry,每一個Entry物件表示儲存在雜湊表中的一個鍵值對。

Entry物件唯一表示一個鍵值對,有四個屬性:

-K key 鍵物件
-V value 值物件
-int hash 鍵物件的hash值
-Entryentry 指向連結串列中下一個Entry物件,可為null,表示當前Entry物件在連結串列尾部

可以說,有多少個鍵值對,就有多少個Entry物件,那麼在HashMap和HashTable中是怎麼儲存這些Entry物件,以方便我們快速查詢和修改的呢?請看下圖。

上圖畫出的是一個桶數量為8,存有5個鍵值對的HashMap/HashTable的記憶體佈局情況。可以看到HashMap/HashTable內部建立有一個Entry型別的引用陣列,用來表示雜湊表,陣列的長度,即是雜湊桶的數量。而陣列的每一個元素都是一個Entry引用,從Entry物件的屬性裡,也可以看出其是連結串列的節點,每一個Entry物件內部又含有另一個Entry物件的引用。

這樣就可以得出結論,HashMap/HashTable內部用Entry陣列實現雜湊表,而對於對映到同一個雜湊桶(陣列的同一個位置)的鍵值對,使用Entry連結串列來儲存(解決hash衝突)。

以下程式碼及註釋來自java.util.HashTable

/**
* The hash table data.
*/

private transient Entry<K,V>[] table;


以下程式碼及註釋來自java.util.HashMap

/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

從程式碼可以看到,對於雜湊桶的內部表示,兩個類的實現是一致的。

14.2 演算法

上一小節已經說了用來表示雜湊表的內部資料結構。HashMap/HashTable還需要有演算法來將給定的鍵key,對映到確定的hash桶(陣列位置)。需要有演算法在雜湊桶內的鍵值對多到一定程度時,擴充雜湊表的大小(陣列的大小)。本小節比較這兩個類在演算法層面有哪些不同。

初始容量大小和每次擴充容量大小的不同。先看程式碼:

以下程式碼及註釋來自java.util.HashTable

// 雜湊表預設初始大小為11
public Hashtable() {
  this(110.75f);
}

protected void rehash() {
  int oldCapacity = table.length;
  Entry<K,V>[] oldMap = table;

  // 每次擴容為原來的2n+1
  int newCapacity = (oldCapacity << 1) + 1;
  // ...
}


以下程式碼及註釋來自java.util.HashMap

// 雜湊表預設初始大小為2^4=16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4// aka 16

void addEntry(int hash, K key, V valueint bucketIndex{
  // 每次擴充為原來的2n 
  if ((size >= threshold) && (null != table[bucketIndex])) {
     resize(2 * table.length);
}

可以看到HashTable預設的初始大小為11,之後每次擴充為原來的2n+1。HashMap預設的初始化大小為16,之後每次擴充為原來的2倍。還有我沒列出程式碼的一點,就是如果在建立時給定了初始化大小,那麼HashTable會直接使用你給定的大小,而HashMap會將其擴充為2的冪次方大小。

也就是說HashTable會盡量使用素數、奇數。而HashMap則總是使用2的冪作為雜湊表的大小。我們知道當雜湊表的大小為素數時,簡單的取模雜湊的結果會更加均勻,所以單從這一點上看,HashTable的雜湊表大小選擇,似乎更高明些。

但另一方面我們又知道,在取模計算時,如果模數是2的冪,那麼我們可以直接使用位運算來得到結果,效率要大大高於做除法。所以從hash計算的效率上,又是HashMap更勝一籌。

所以,事實就是HashMap為了加快hash的速度,將雜湊表的大小固定為了2的冪。當然這引入了雜湊分佈不均勻的問題,所以HashMap為解決這問題,又對hash演算法做了一些改動。具體我們來看看,在獲取了key物件的hashCode之後,HashTable和HashMap分別是怎樣將他們hash到確定的雜湊桶(Entry陣列位置)中的。

以下程式碼及註釋來自java.util.HashTable

// hash 不能超過Integer.MAX_VALUE 所以要取其最小的31個bit
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;

// 直接計算key.hashCode()
private int hash(Object k) {
  // hashSeed will be zero if alternative hashing is disabled.
  return hashSeed ^ k.hashCode();
}


以下程式碼及註釋來自java.util.HashMap
int hash = hash(key);
int i = indexFor(hash, table.length);

// 在計算了key.hashCode()之後,做了一些位運算來減少雜湊衝突
final int hash(Object k) {
  int h = hashSeed;
  if (0 != h && k instanceof String) {
      return sun.misc.Hashing.stringHash32((String) k);
  }

  h ^= k.hashCode();

  // This function ensures that hashCodes that differ only by
  // constant multiples at each bit position have a bounded
  // number of collisions (approximately 8 at default load factor).
  h ^= (h >>> 20) ^ (h >>> 12);
  return h ^ (h >>> 7) ^ (h >>> 4);
}

// 取模不再需要做除法
static int indexFor(int h, int length) {
  // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
  return h & (length-1);
}

正如我們所言,HashMap由於使用了2的冪次方,所以在取模運算時不需要做除法,只需要位的與運算就可以了。但是由於引入的hash衝突加劇問題,HashMap在呼叫了物件的hashCode方法之後,又做了一些位運算在打散資料。關於這些位計算為什麼可以打散資料的問題,本文不再展開了。

如果你有細心讀程式碼,還可以發現一點,就是HashMap和HashTable在計算hash時都用到了一個叫hashSeed的變數。這是因為對映到同一個hash桶內的Entry物件,是以連結串列的形式存在的,而連結串列的查詢效率比較低,所以HashMap/HashTable的效率對雜湊衝突非常敏感,所以可以額外開啟一個可選hash(hashSeed),從而減少雜湊衝突。

因為這是兩個類相同的一點,所以本文不再展開了。事實上,這個優化在JDK 1.8中已經去掉了,因為JDK 1.8中,對映到同一個雜湊桶(陣列位置)的Entry物件,使用了紅黑樹來儲存,從而大大加速了其查詢效率。

相關推薦

HashMap HashTable 到底不同

時間 txt instance 數據 dset 修改 keyset adding ould HashMap 和 HashTable 到底哪不同 ? 2017/05/29 | 分類: 基礎技術 | 1 條評論 | 標簽: HASHMAP, HASHTABLE

HashMapHashTable到底不同

HashMap和HashTable有什麼不同?在面試和被面試的過程中,我問過也被問過這個問題,也見過了不少回答,今天決定寫一寫自己心目中的理想答案。 JDK每一版本都在改進。本文討論的HashMap和HashTable基於JDK 1.7.0_67。

HashMap HashTable 不同

程式碼版本 JDK每一版本都在改進。本文討論的HashMap和HashTable基於JDK 1.7.0_67 1. 時間 HashTable產生於JDK 1.1, 而HashMap產生於JDK 1.2。

Arraylistlinkedlist的區別,hashmaphashtable的區別,hashmaphashset的相同與不同,hashcode的用法

Arraylist和linkedlist的區別 相當於陣列和連結串列的區別:當arraylist中新增物件時對應的陣列長度就要改變,因此便於查詢(直接get(i)就ok)而不利於增刪改;相反linkedlist相當於連結串列可以進行增刪改,但是查詢要從第一個節點開始會浪費時

HashMapHashtable的區別

性能 刪除 影響 之間 fail 創建 允許 hashmap 以及 HashMap和Hashtable都實現了Map接口,但決定用哪一個之前先要弄清楚它們之間的分別。主要的區別有:線程安全性,同步(synchronization),以及速度。 HashMap幾乎可以等價於

淺析HashMapHashtable的區別

兩個 ble dem pub 實現 value key-value span div HashMap和Hashtable兩個類都實現了Map接口,二者保存鍵值對(key-value對); HashMap和HashTable區別 第一,繼承的父類不同。HashMap繼承自A

HashMapHashtable存放null

war read ash nal style () exce point entry Hashmap是可以放key為null的,Hashtable不能放key為null。hashtable放key為null會報空指針異常 1. hashmap put方法源碼 public

java讀書筆記---HashMapHashTable

多個 大小 c語言 先來 方法 內部實現 計算 iterator put 首先來說說HashMap,HashMap是一個類,Java中所有的類都繼承自一個Object類。Object類中定義了hashCode()方法,換言之,任何類都會有這個hashCode()方法。 因此

hashmaphashtable

key值 tor xtend 對象 extend 不能 num table 解決 在這裏幫大家總結一下hashMap和hashtable方面的知識點吧: 1. 關於HashMap的一些說法: a) HashMap實際上是一個“鏈表散列”的數據結構,即數組和鏈表的結合體

hashMap hashTable

輕量 ron ash 實體 方法 value inter bject class hashMap非常好用,它的Key-Value剛好對應object的屬性和值,可以免去創建一些Model實體類 hashMap和HashTable作用好像比較相似,下面是些比較

java 的HashMapHashTable的區別?

鏈表結構 線程 數組 操作 map 內部實現 鏈表 hashmap lin  1.HashMap和HashTable的區別?    HashMap不是線程安全的它的操作方法沒有進行同步處理    HashMap允許key為空        HashTable是線程安全的它的

HashMap HashTable 區別

AC n) ati 只需要 試圖 external str enume 時間 來源:http://www.importnew.com/7010.html HashMap和Hashtable的區別 HashMap和Hashtable都實現了Map接口,但決定用哪一個之前先要弄

HashMapHashtable有什麽區別?

map tab ble 線程 table ash 什麽 shm 多線程 HashMap和Hashtable都是實現Map接口的,但是:   1.HashMap允許鍵和值都是null的,而Hashtable不允許鍵和值為null   2.Hashtable是同步的,而Hash

HashMapHashtable區別

-- 其它 serial on() 很大的 程序 fas cloneabl was 1. 類定義 這個從源碼中可以直接看出來,HashMap 繼承自 AbstractMap,而 Hashtabl 繼承自 Dictionary。 public class HashMap<

談談我所理解的HashMapHashTable

cti null 線程安全 單線程 end bst 不同 extend 環境 HashMap和HashTable之間的聯系和區別如下: 1.HashMap幾乎可以等價於Hashtable,但是它們之間繼承不同:HashMap extends Ab

HashMapHashtable的區別 學習筆記

ble new map 面試題 學習筆記 ring println 線程 1.2 /** * @param args * 面試題 * HashMap和Hashtable的區別 * 共同點: * 底層都是哈希算法,都是雙列集合

【java】 HashMap的工作原理+HashMapHashtable的區別+HashMapHashSet的區別

本文由 ImportNew - 唐小娟 翻譯自 Javarevisited。 HashMap的工作原理是近年來常見的Java面試題。幾乎每個Java程式設計師都知道HashMap,都知道哪裡要用HashMap,知道Hashtable和HashMa

Collection與Collections、ArrayListVector、HashMapHashtable(面試常用)

Collections與Collection 1. Collections是java.util下的類,它包含有各種有關集合操作的靜態方法2. Collection是java.util下的介面,它是各種集合結構的父介面   ArrayList和Vector1. 同步性: Vector是執行緒安全

HashMapHashTable的異同點

HashMap和HashTable異同點 底層資料: HashMap和HashTable底層資料結構相同,都是以陣列加連結串列形式儲存資料。 繼承關係: HashMap和HashTable都實現了Clonable ,Map,Serializable介面。 但不同的是HashTable

關於hashmaphashtable的區別,及如何使hashmap變得執行緒安全?(除了synchronized)---concurrentHashmap

我們都知道hashmap是執行緒不安全的,而效率也比較高,他允許我們存入null鍵及null值; 而 hashtable 是執行緒安全的,其效率比較低,不允許我們存入null鍵和null值; 除了非同步及允許使用null值,hashmap與hashtable基本相同; 那麼為什麼hash