HashMap 和 Hashtable 这属于双列集合Map的一个实现类,是以键值对即Key-Value的形式存储的,在面试的时候是经常问到的,但是很多人都回答的不够全面,现在我看完一些资料来总结一下
1、线程安全
Hashtable 是线程安全的,HashMap 不是线程安全的。
原因是什么呢,可以看看Hashtable的源码部分
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 = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
Hashtable 所有的元素操作都是 synchronized 修饰的,而 HashMap 并没有。
2、性能方面
既然 Hashtable 是线程安全的,每个方法都要阻塞其他线程,所以 Hashtable 性能较差,HashMap 性能较好,使用更广。
这个时候就要想到我既想效率高,又能相对安全一点,那就得是JUC 包下的 ConcurrentHashMap了。
3.空值问题NULL
Hashtable 是不允许键或值为 null 的,HashMap 的键值则都可以为 null。
是什么原因导致的这种情况的呢
我们来看一下源码
首先Hashtable的
*/
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 = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
再来看下HashMap的
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
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);
}
可以看出 Hashtable key 为 null 会直接抛出空指针异常,value 为 null 手动抛出空指针异常,而 HashMap 的逻辑对 null 作了特殊处理。
以上这三种就是我们面试和最容易想起来的,
4.实现方式
继承源码不同,
Hashtable
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
HashMap
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
可以看出两者实现的类不一样,Hashtable 继承了 Dictionary类,而 HashMap 继承的是 AbstractMap 类。
5.扩容
HashMap 的初始容量为:16,Hashtable 初始容量为:11,两者的负载因子默认都是:0.75。
源码奉上
HashMap
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
Hashtable
/**
* Constructs a new, empty hashtable with a default initial capacity (11)
* and load factor (0.75).
*/
public Hashtable() {
this(11, 0.75f);
}
当现有容量大于总容易 * 负载因子时,HashMap 扩容规则为当前容量翻倍,Hashtable 扩容规则为当前容量翻倍 + 1。
6.最后一个就是迭代器了
HashMap 中的 Iterator 迭代器是 fail-fast 的,而 Hashtable 的 Enumerator 不是 fail-fast 的。
所以,当其他线程改变了HashMap 的结构,如:增加、删除元素,将会抛出 ConcurrentModificationException 异常,而 Hashtable 则不会。
Enumeration是个接口,不是类,再次,这个东西就是为了实现遍历的,现在已经被迭代器Iterator取代了
Enumeration和iterator最主要区别,其实就是Iterator可以删除元素,但是Enumration却不能。