[英]Why Hashtable does not allow null keys or values?
正如 JDK 文檔中所指定的,Hashtable 不允許空鍵或值。 HashMap 允許一個空鍵和任意數量的空值。 為什么是這樣?
Hashtable 是較老的類,通常不鼓勵使用它。 也許他們看到需要一個空鍵,更重要的是 - 空值,並將其添加到 HashMap 實現中。
HashMap 比較新,有更高級的能力,基本上只是對Hashtable 功能的改進。 在創建 HashMap 時,它專門設計用於將空值作為鍵處理並將它們作為特殊情況處理。
編輯
從Hashtable
JavaDoc :
要成功地從 Hashtable 存儲和檢索對象,用作鍵的對象必須實現 hashCode 方法和 equals 方法。
由於null
不是對象,因此不能對其調用.equals()
或.hashCode()
,因此Hashtable
無法計算散列以將其用作鍵。
Hashtable 和 ConcurrentHashMap 不允許空鍵或值的主要原因是因為期望它們將在多線程環境中使用。 一分鍾,讓我們假設允許空值。 在這種情況下,哈希表的“get”方法具有不明確的行為。 如果在映射中找不到鍵,它可以返回 null,如果找到鍵並且它的值為 null,它可以返回 null。 當代碼需要空值時,它通常會檢查映射中是否存在鍵,以便知道鍵是不存在還是鍵存在但值為空。 現在這段代碼在多線程環境中中斷了。 讓我們看看下面的代碼:
if (map.contains(key)) {
return map.get(key);
} else {
throw new KeyNotFoundException;
}
在上面的代碼中,假設線程 t1 調用 contains 方法並找到鍵,它假定鍵存在並准備好返回值,無論它是否為空。 現在,在它調用 map.get 之前,另一個線程 t2 從地圖中刪除該鍵。 現在 t1 恢復並返回 null。 但是,根據代碼,t1 的正確答案是 KeyNotFoundException,因為密鑰已被刪除。 但它仍然返回空值,因此預期的行為被破壞了。
現在,對於常規 HashMap,假設它將被單個線程調用,因此不可能在“包含”檢查和“獲取”中間刪除鍵。 所以 HashMap 可以容忍空值。 然而,對於 Hashtable 和 ConcurrentHashMap,期望很明確,即多個線程將對數據進行操作。 因此,他們不能允許空值並給出不正確的答案。 同樣的邏輯也適用於鍵。 現在 counter 參數可以是 - 對於 Hashtables 和 ConcurrentHashMaps 的非空值,包含和獲取步驟可能會失敗,因為另一個線程可以在執行第二步之前修改映射/表。 這是正確的,它可能發生。 但是由於 Hashtables 和 ConcurrentHashMaps 不允許空鍵和值,因此它們沒有必要首先實現 contains 和 get check。 他們可以直接獲取值,因為他們知道如果 get 方法返回 null,唯一的原因是鍵不存在,而不是因為值可能為 null。 contains 和 get 檢查只對 HashMap 是必要的,因為它們允許空值,因此需要解決關於是否找不到鍵或值為空的歧義。
原因是接受的答案的原因:Hashtable 很舊。
但是,不鼓勵在每種情況下使用 Hashtable 來支持 HashMap。
Hashtable 和 ConcurrentHashMap 都不支持 null 鍵或值。 HashMap 確實如此。
如果您想要一個插入式替換,除了更改類之外不需要任何其他內容並且在每種情況下都可以使用,那么沒有。 最相似的選項是ConcurrentHashMap (它是線程安全的,但不支持鎖定整個表):
在依賴其線程安全但不依賴於其同步細節的程序中,此類與 Hashtable 完全可互操作。
HashMap 是單線程應用程序的更好替代品,或者不需要任何時間同步,因為同步引入了性能影響。
資料來源:
默認 Hashtable 實現具有空檢查,這會引發空指針異常。 后來 Java 開發人員可能已經意識到空鍵(對於某些默認值等)和值的重要性,這也是 HashMap 被引入的原因。
對於 HashMap,如果鍵為空,則對鍵進行空檢查,則該元素將存儲在不需要哈希碼的位置。
所以總結
因為在 HashTable 中,當您放置元素時,它會考慮鍵和值哈希。 基本上你會有類似的東西:
public Object put(Object key, Object value){
key.hashCode();
//some code
value.hashCode();
}
HashTable - 不允許空鍵 這是因為,在 put(K key, V value) 方法中,我們有 key.hashcode() 會拋出空指針異常。 HashTable - 不允許空值這是因為,在 put(K key, V value) 方法中,我們有 if(value==null){throw new NullPointerException
HashMap 允許空值,因為它沒有像 HashTable 這樣的任何檢查,而它只允許一個空鍵。 這是在 putForNullKey 方法的幫助下完成的,該方法在每次將鍵提供為 null 時將值添加到內部數組的第 0 個索引
哈希表是非常古老的類,來自 JDK 1.0
要理解這一點,首先我們需要理解作者在這個類上寫的評論。 “這個類實現了一個哈希表,它將鍵映射到值。 任何非空對象都可以用作鍵或值。 要成功地從哈希表中存儲和檢索對象,用作鍵的對象必須實現 hashCode 方法和 equals 方法。”
HashTable 類是在哈希機制上實現的,這意味着存儲任何鍵值對,其所需的鍵對象哈希碼。 如果鍵為空,它將無法給出哈希值,它將通過空指針異常和類似的值情況,如果值為空,則拋出空值。
但后來人們意識到空鍵和值有其自身的重要性,這就是為什么在后來實現的類(如 HashMap 類)中允許一個空鍵和多個空值的原因。
對於哈希映射,空鍵將允許,並且如果鍵為空,則對鍵進行空檢查,則該元素將存儲在 Entry 數組中的零位置。 我們可以將空鍵用於某些默認值..
=> Hashtable 方法是同步的,它從不使用基於對象的鎖定。
HashMap 的實現是因為它的特殊性
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
Java 8 你不能推斷哈希表的類型。
private Map<String,String> hashtable = new Hashtable<>(); // Not Allowed
private Map<String,String> hashtable = new HashMap<>(); // Allowed
正如@Jainendra 所說,HashTable 不允許在put()
調用 key.hashCode() 為空鍵。
但似乎沒有人明確回答為什么不允許空值。
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;
}
put 中的空檢查並沒有解釋為什么空值是非法的,它只是確保非空不變。
不允許空值的具體答案是 HashTable 在調用contains/remove
時會調用value.equals
。
Hashtable 不允許空鍵,但 HashMap 允許一個空鍵和任意數量的空值。 這背后有一個簡單的解釋。
當 null作為 key傳遞並且 null Key 作為特殊情況處理時,hashmap中的put()
方法不會調用 hashcode() 。 HashMap 將空鍵放在桶 0 中並將空作為鍵映射到傳遞的值。
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
如算法中所見, put()
方法檢查鍵是否為空並調用 putForNullKey(value) 並返回。 這個 putForNullKey 將在桶中的 0 索引處創建一個條目。 索引零始終為存儲桶中的空鍵保留。
另一方面,如果哈希表對象用作鍵,則必須實現 hashCode 方法和 equals方法。 由於 null 不是對象,因此無法實現這些方法。
Hashtable 是 Java 的第一個版本附帶的一個類。 當它發布時,Java 工程師試圖阻止使用空鍵,或者可能沒有意識到它的用處。 因此,他們不允許在 Hashtable 中使用它。
如果 value 為 null,則在Hashtable 中插入鍵值對的 put 方法拋出 NullPointerException 。 由於Hashtable是基於hash機制的,所以對key進行hash計算,如果key為null則拋出NullPointerException。
后來的 Java 工程師一定已經意識到,擁有一個空鍵和值有其用途,就像將它們用於默認情況一樣。 因此,他們在 Java 5 中為 HashMap 類提供了具有存儲空鍵和值的能力的集合框架。
在 HashMap 中插入鍵值對的 put 方法檢查空鍵並將其存儲在內部表數組的第一個位置。 它不怕空值,也不會像 Hashtable 那樣拋出 NullPointerException。
現在,只能有一個空鍵,因為鍵必須是唯一的,盡管我們可以有多個與不同鍵相關聯的空值。
HashTable - 不允許空鍵
這是因為,在 put(K key, V value) 方法中,我們有key.hashcode()
會拋出空指針異常。
HashTable - 不允許空值
這是因為,在 put(K key, V value) 方法中,我們有if(value==null){throw new NullPointerException
HashMap 允許空值,因為它沒有像 HashTable 這樣的任何檢查,而它只允許一個空鍵。 這是在 putForNullKey 方法的幫助下完成的,該方法在每次將鍵提供為 null 時將值添加到內部數組的第 0 個索引
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.