为啥 Hashtable 不允许空键或空值?

Posted

技术标签:

【中文标题】为啥 Hashtable 不允许空键或空值?【英文标题】:Why Hashtable does not allow null keys or values?为什么 Hashtable 不允许空键或空值? 【发布时间】:2012-08-12 11:44:13 【问题描述】:

如 JDK 文档中所述,Hashtable 不允许空键或空值。 HashMap 允许一个空键和任意数量的空值。这是为什么呢?

【问题讨论】:

因为key不能在一个map中重复。 也许这个answer 会帮助你。 哈希表希望通过这样做来强制执行合同。此协定确保应用于哈希表的 get(.) 方法当且仅当键不在映射中时才会返回 null。 Why does Hashtable not take null key?的可能重复 【参考方案1】:

Hashtable 是较旧的类,通常不鼓励使用它。也许他们看到了对空键的需求,更重要的是 - 空值,并将其添加到 HashMap 实现中。

HashMap 较新,具有更高级的功能,基本上只是对 Hashtable 功能的改进。 HashMap 在创建时,专门设计用于将 null 值作为键处理,并将它们作为特殊情况处理。

编辑

来自HashtableJavaDoc:

要成功地从 Hashtable 存储和检索对象, 用作键的对象必须实现 hashCode 方法和 equals 方法。

由于null 不是对象,因此您不能在其上调用.equals().hashCode(),因此Hashtable 无法计算哈希以将其用作键。

【讨论】:

ConcurrentHashMap 是一个较新的类,但也有不允许空键或空值的限制。他们出于性能原因添加了此限制,因为支持 null 键和值需要做很多额外的工作,但在大多数情况下可能没有用。 从答案中我期待为什么 Hashtable 不允许空键或值的原因?虽然你建议更新和旧。 我认为因为 hashtable 是同步的,它不能持有或锁定空对象【参考方案2】:

Hashtable 和 ConcurrentHashMap 不允许空键或空值的主要原因是因为期望它们将在多线程环境中使用。让我们假设允许空值。在这种情况下,哈希表的“get”方法具有模棱两可的行为。如果在 map 中找不到键,它可以返回 null,如果找到键并且它的值为 null,它可以返回 null。当代码需要空值时,它通常会检查键是否存在于映射中,以便它可以知道键是否不存在或键存在但值为空。现在这段代码在多线程环境中中断。我们来看看下面的代码:

if (map.contains(key)) 
    return map.get(key);
 else 
    throw new KeyNotFoundException;

在上面的代码中,假设线程 t1 调用 contains 方法并找到键,它假定键存在并准备返回值,无论它是否为空。现在在它调用 map.get 之前,另一个线程 t2 从映射中删除该键。现在 t1 恢复并返回 null。但是根据代码, t1 的正确答案是 KeyNotFoundException 因为密钥已被删除。但它仍然返回 null,因此预期的行为被破坏了。

现在,对于常规的 HashMap,假设它将由单个线程调用,因此在“包含”检查和“获取”中间不可能删除键。所以 HashMap 可以容忍空值。但是对于 Hashtable 和 ConcurrentHashMap,期望很明显,多个线程将对数据进行操作。因此,他们不能允许空值并给出不正确的答案。同样的逻辑也适用于键。现在 counter 参数可以是 - 对于 Hashtables 和 ConcurrentHashMaps 的非空值,包含和获取步骤可能会失败,因为另一个线程可以在执行第二步之前修改映射/表。这是正确的,它可能发生。但是由于 Hashtables 和 ConcurrentHashMaps 不允许空键和值,所以它们没有必要首先实现包含和获取检查。他们可以直接获取值,因为他们知道如果 get 方法返回 null,唯一的原因是键不存在,而不是因为值可能为 null。 contains 和 get 检查仅对 HashMaps 是必需的,因为它们允许空值,因此需要解决有关是否找不到键或值是否为空的歧义。

【讨论】:

【参考方案3】:

原因是接受答案的原因:Hashtable is old.

但是,在每种情况下,不鼓励使用 Hashtable 来支持 HashMap。

哈希表是同步的,所以是THREAD-SAFE。 HashMap 不是。

Hashtable 和 ConcurrentHashMap 都不支持空键或空值。 HashMap 可以。

如果您想要一个只需要更改类并且在每种情况下都可以使用的直接替代品,那么没有。最相似的选项是ConcurrentHashMap(它是线程安全的,但不支持锁定整个表):

这个类在依赖的程序中与 Hashtable 完全可互操作 关于它的线程安全,而不是它的同步细节。

HashMap 是单线程应用程序的更好替代品,或者不需要任何时间同步,因为同步引入了性能影响。

来源:

Hashtable HashMap ConcurrentHashMap

【讨论】:

ConcurrentHashMap put:将指定键映射到此表中的指定值。键和值都不能为空。请检查您的答案。 哎呀!谢谢你的提醒!我很明显,认为 ConcurrentHashMap 和 HashMap 之间的唯一区别是线程安全。这不是第一次发生。多么尴尬的命名约定!【参考方案4】:

总结一下

因为在 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 个索引

【讨论】:

废话。 Hashtable 从不调用value.hashCode()【参考方案5】:

默认的 Hashtable 实现有空值检查,这会导致空指针异常。 后来 java 开发者可能已经意识到空键(对于一些默认值等)和值的重要性以及引入 HashMap 的原因。

对于 HashMap,如果键为空,则对键进行空检查,然后该元素将存储在不需要哈希码的位置。

【讨论】:

【参考方案6】:

哈希表是非常古老的类,从 JDK 1.0 开始

要理解这一点,首先我们需要理解作者在这个类上写的cmets。 “这个类实现了一个哈希表,它将键映射到值。任何非空对象都可以用作键或值。 要成功地从哈希表中存储和检索对象,用作键的对象必须实现 hashCode 方法和 equals 方法。”

HashTable 类是在散列机制上实现的,即存储任意键值对,其所需要的键对象的散列码。如果 key 为 null,它将无法给出 hash,它将通过 null 指针异常和类似的 value 情况 如果值为 null,则抛出 null。

但后来才意识到 null 键和值有其自身的重要性,即 为什么在后来实现的类(如 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

【讨论】:

【参考方案7】:

正如@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 中的 null 检查并不能解释为什么 null 值是非法的,它只是确保非 null 不变量。

不允许空值的具体答案是当调用contains/remove时HashTable会调用value.equals

【讨论】:

【参考方案8】:

Hashtable 不允许空键,但 HashMap 允许一个空键和任意数量的空值。这背后有一个简单的解释。

put() hashmap 中的方法在传递 null 作为 key 时不调用 hashcode(),并且 null Key 作为特殊情况处理。 HashMap 将 null 键放入桶 0 并将 null 作为键映射到传递的值。

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 不是对象,因此无法实现这些方法。

【讨论】:

【参考方案9】:

Hashtable 是 java 的第一个版本附带的一个类。当它发布时,Java 工程师试图阻止使用空键,或者可能没有意识到它的用处。因此,他们不允许在 Hashtable 中使用它。

如果 value 为 null,则在 Hashtable 中插入键值对的 put 方法会抛出 NullPointerException。由于Hashtable是基于散列机制的,所以对key计算hash,key为null时会抛出NullPointerException。

后来的 Java 工程师一定已经意识到,拥有一个空键和空值有它的用途,就像在默认情况下使用它们一样。因此,他们在 Java 5 中提供了带有集合框架的 HashMap 类,具有存储空键和值的能力。

在HashMap中插入键值对的put方法检查空键并将其存储在内部表数组的第一个位置。它不怕 null 值,也不会像 Hashtable 那样抛出 NullPointerException。

现在,只能有一个空键,因为键必须是唯一的,尽管我们可以有多个空值与不同的键关联。

【讨论】:

【参考方案10】:

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 个索引

【讨论】:

类名是 java.util.Hashtable 而不是 "HashTable" !

以上是关于为啥 Hashtable 不允许空键或空值?的主要内容,如果未能解决你的问题,请参考以下文章

在 Java 中向 HashMap 添加空键或值有啥用?

为啥 TreeMap 中不允许空键?

Java常见面试题分享

Java集合框架中的Hashtable,HashMap,HashSet,哈希表概念

细读源码之-HashMap

具有空键和空值的 HashMap