簡體   English   中英

為什么 Hashtable 不允許空鍵或值?

[英]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 是同步的,所以它是THREAD-SAFE 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM