一 简介
1、概念
和HashMap一样,Hashtable也是一个散列表,它存储的内容是键值对。HashTable与ArrayList一样,是非泛型的,value存进去是object,存取会发生装箱、拆箱。
2、成员变量
table:一个 Entry[] 数组类型
count:Hashtable 的大小,它是 Hashtable 保存的键值对的数量。
threshold:Hashtable 的阈值,用于判断是否需要调整 Hashtable 的容量。threshold 的值=”容量*加载因子”。
loadFactor:加载因子。
modCount:用来实现 fail-fast 机制的。
3、构造方法
//默认的构造器,将会以默认的初始容量(11)和默认的平衡因子(0.75)来初始化内部的数组
public Hashtable();
//用指定的初始容量和默认的平衡因子(0.75)来初始化内部的数组
public Hashtable(int initialCapacity);
//用指定的初始容量和指定的平衡因子来初始化内部的数组
public Hashtable(int initialCapacity, float loadFactor);
//用一个Map对象来构建,并将该Map中的元素添加到Hashtable
public Hashtable(Map<? extends K, ? extends V> t);
4、成员方法概览
//置空操作
public synchronized void clear();
//查看该value是否在Hashtable中存在
public synchronized boolean contains(Object value);
//查看该key是否在Hashtable中存在
public synchronized boolean containsKey(Object key);
//该方法与contains()方法一样
public boolean containsValue(Object value);
//返回“所有value”的枚举对象
public synchronized Enumeration<V> elements();
//
public Set<Map.Entry<K,V>> entrySet();
//重写equals()函数
public synchronized boolean equals(Object o);
//返回key对应的value,没有的话返回null
public synchronized V get(Object key);
//
public synchronized int hashCode();
//返回该Hashtable是否为空
public synchronized boolean isEmpty();
//返回此哈希表中的键的枚举。
public synchronized Enumeration<K> keys();
//返回该Hashtable中包含所有键的 Set 视图。
public Set<K> keySet();
//将指定 key 映射到此哈希表中的指定 value。
public synchronized V put(K key, V value);
//将指定映射的所有映射关系复制到此哈希表中,这些映射关系将替换此哈希表拥有的、针对当前指定映射中所有键的所有映射关系。
public synchronized void putAll(Map<? extends K, ? extends V> t);
//从哈希表中移除该键及其相应的值。
public synchronized V remove(Object key);
// 返回此哈希表中的键的数量。
public synchronized int size();
//返回此 Hashtable 对象的字符串表示形式,其形式为 ASCII 字符 ", " (逗号加空格)分隔开的、括在括号中的一组条目。
public synchronized String toString();
//返回该Hashtable中 值的Collection视图
public Collection<V> values();
5、特点
1.value不能为null
二 源码解析
1、hash算法解析
private int hash(Object k) {
// hashSeed will be zero if alternative hashing is disabled.
return hashSeed ^ k.hashCode();
}
其中initHashSeedAsNeeded方法用于初始化hashSeed参数,其中hashSeed用于计算key的hash值,它与key的hashCode进行按位异或运算。这个hashSeed是一个与实例相关的随机值,主要用于解决hash冲突。
2、hash值到内存地址映射算法解析
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
3、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 = 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;
}
4、扩容操作
protected void rehash() {
int oldCapacity = table.length;
Entry<K,V>[] oldMap = table;
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<K,V>[] newMap = new Entry[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
boolean rehash = initHashSeedAsNeeded(newCapacity);
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
if (rehash) {
e.hash = hash(e.key);
}
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
Hashtable的扩容操作,首先重新计算新的容量大小,为旧的容量大小的二倍加1。然后新new一个新的Entry数组,并且计算新的阀值。最后将旧数组中的Entry转移到新的Entry数组中。
5、线程安全的实现
Hashtable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下Hashtable的效率非常低下。因为当一个线程访问Hashtable的同步方法时,其他线程访问Hashtable的同步方法时,可能会进入阻塞或轮询状态。
注:Hashtable本身的操作是线程安全的,但是如果Hashtable中key所对应的值是一个对象的引用,对此对象进行修改操作无法自动保证线程安全。
三 遍历方式
1、使用keys()
Enumeration<String> en1 = table.keys();
while(en1.hasMoreElements()) {
en1.nextElement();
}
2、使用elements()
Enumeration<String> en2 = table.elements();
while(en2.hasMoreElements()) {
en2.nextElement();
}
3、使用keySet()
Iterator<String> it1 = table.keySet().iterator();
while(it1.hasNext()) {
it1.next();
}
4、使用entrySet()
Iterator<Entry<String, String>> it2 = table.entrySet().iterator();
while(it2.hasNext()) {
it2.next();
}
四 应用
1、Hashtable与HashMap的比较
1)HashMap 的 key 和 value 都允许为 null,而 Hashtable 的 key 和 value 都不允许为 null。HashMap 遇到 key 为 null 的时候,调用 putForNullKey 方法进行处理,而对 value 没有处理;Hashtable遇到 null,直接返回 NullPointerException。
2)Hashtable 方法是同步,而HashMap则不是。我们可以看一下源码,Hashtable 中的几乎所有的 public 的方法都是 synchronized 的,而有些方法也是在内部通过 synchronized 代码块来实现。所以有人一般都建议如果是涉及到多线程同步时采用 HashTable,没有涉及就采用 HashMap。
3)HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。