今天来和大家唠唠HashTable,平时编码的时候没用过它,记忆中它和HashMap一样是进行key-value存储的,数据结构也一样,但是HashMap不是线程安全的,HashTable是线程安全的。今天我们就来剖析一下它,因为和HashMap相似,所以对比着来看。
真的想吐槽下:Hashtable 为啥不是HashTable[捂脸]
一、相同之处
- HashMap和HashTable数据结构是一致的。内部都是一个Entry<K,V>的数组。
private static class Entry<K,V> implements Map.Entry<K,V> {
int hash;
final K key;
V value;
Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
- 内部成员变量一致。loadFactor threshold modCount count(HashMap中数量用size表示)在Hashtable中均有。
- 同HashMap一样,当count>=threshold时就需要进行扩容了。
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
......
}
二、不同之处
- Hashtable初始化时,默认的容量是11,负载因子是0.75,且初始化的时候就会为Entry<K,V>数组申请。
public Hashtable() {
this(11, 0.75f);
}
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry[initialCapacity]; //申请table数组空间
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
initHashSeedAsNeeded(initialCapacity);
}
- HashMap能存储key为null的情况,Hashtable不能存储key为null的。
- 扩容机制不同。HashMap每次resize的时候,容量会扩容为原来的2倍,Hashtable会扩容为原来的2倍+1(为保证奇数)
- HashMap不是线程安全的(具体原因见:https://www.jianshu.com/p/c92e0e279243),Hashtable是线程安全的。实现的方式是通过在方法上加synchronized锁,如下:
public synchronized V get(Object key) {
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)) {
return e.value;
}
}
return null;
}
Hashtable提供的所有方法都加上了synchronized锁。虽然保证了线程安全,但同时我们也发现,在每个方法上都加了这个同步锁,在多并发的情况下,效率是很低的。