如 JDK 文档中所述,Hashtable 不允许空键或空值。HashMap 允许一个空键和任意数量的空值。为什么是这样?
10 回答
Hashtable 是较老的类,通常不鼓励使用它。也许他们看到了对空键的需求,更重要的是 - 空值,并将其添加到 HashMap 实现中。
HashMap 较新,并且具有更高级的功能,基本上只是对 Hashtable 功能的改进。创建 HashMap 时,它专门设计为将 null 值作为键处理,并将它们作为特殊情况处理。
编辑
来自Hashtable
JavaDoc:
要成功地从 Hashtable 存储和检索对象,用作键的对象必须实现 hashCode 方法和 equals 方法。
由于null
不是对象,因此您无法调用.equals()
或.hashCode()
对其进行调用,因此Hashtable
无法计算散列以将其用作键。
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 检查仅对 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 时将值添加到内部 Array 的第 0 个索引
原因是接受答案的原因:Hashtable is old。
但是,在每种情况下都不鼓励使用 Hashtable 来支持 HashMap。
- Hashtable 是同步的,所以它是THREAD-SAFE。HashMap 不是。
Hashtable 和 ConcurrentHashMap 都不支持空键或空值。HashMap 可以。
如果您想要一个只需要更改类并且在每种情况下都可以使用的直接替代品,那么没有。最相似的选项是ConcurrentHashMap(它是线程安全的,但不支持锁定整个表):
在依赖线程安全但不依赖同步细节的程序中,此类与 Hashtable 完全可互操作。
HashMap 是单线程应用程序的更好替代品,或者不需要任何时间同步,因为同步引入了性能影响。
资料来源:
默认 Hashtable 实现具有空值检查,这会导致空指针异常。后来,Java 开发人员可能已经意识到空键(对于某些默认值等)和值的重要性以及引入 HashMap 的原因。
对于 HashMap,如果键为空,则对键进行空检查,然后该元素将存储在不需要哈希码的位置。
正如@Jainendra 所说,HashTable 不允许 null 键用于调用 key.hashCode() in put()
。
但似乎没有人清楚地回答为什么不允许空值。
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 不变量。
不允许空值的具体答案是 HashTable 将在 callvalue.equals
时调用contains/remove
。
Hashtable 是 java 的第一个版本附带的一个类。当它发布时,Java 工程师试图阻止使用空键,或者可能没有意识到它的用处。因此,他们不允许在哈希表中使用它。
如果 value 为 null,则在Hashtable中插入键值对的 put 方法会抛出 NullPointerException。由于Hashtable是基于散列机制的,所以为key计算hash,如果key为null则抛出NullPointerException。
后来的 Java 工程师一定已经意识到,拥有一个空键和空值有它的用途,就像在默认情况下使用它们一样。因此,他们在 Java 5 中提供了带有集合框架的 HashMap 类,该类具有存储空键和值的能力。
在 HashMap 中插入键值对的 put 方法检查空键并将其存储在内部表数组的第一个位置。它不怕空值,也不会像 Hashtable 那样抛出 NullPointerException。
现在,只能有一个空键,因为键必须是唯一的,尽管我们可以有多个空值与不同的键关联。
哈希表是非常古老的类,来自 JDK 1.0
要理解这一点,首先我们需要理解作者在这个类上写的评论。“这个类实现了一个哈希表,它将键映射到值。任何非空对象都可以用作键或值。要成功地从哈希表中存储和检索对象,用作键的对象必须实现 hashCode 方法和 equals 方法。”</p>
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
Hashtable 不允许空键,但 HashMap 允许一个空键和任意数量的空值。这背后有一个简单的解释。
put()
当 null作为 key 传递并且 null Key 作为特殊情况处理时,hashmap 中的方法不会调用 hashcode() 。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 不是对象,因此无法实现这些方法。
HashTable - 不允许空键
这是因为在 put(K key, V value) 方法中,我们有key.hashcode()
抛出空指针异常。
HashTable - 不允许空值
这是因为,在 put(K key, V value) 方法中,我们有if(value==null){throw new NullPointerException
HashMap 允许空值,因为它没有像 HashTable 这样的任何检查,而它只允许一个空键。这是在 putForNullKey 方法的帮助下完成的,该方法每次将键提供为 null 时将值添加到内部 Array 的第 0 个索引