2.15 HashTable
和HashMap不同,HashTable的实现方式完全不同,这点从二者的类继承关系就可以看出端倪来,HashTable和HashMap虽然都实现了Map接口,但是HashTable继承了DIctionary抽象类,而HashMap继承了AbstractMap抽象类。
2.15.1 HashTable的类继承关系图
HashTable
HashMap
2.15.2 Dictionary接口
public abstract
class Dictionary<K,V> {
public Dictionary() {
}
public abstract int size();
public abstract boolean isEmpty();
public abstract Enumeration<K> keys();
public abstract Enumeration<V> elements();
public abstract V get(Object key);
public abstract V put(K key, V value);
public abstract V remove(Object key);
}
Dictionary类中有这样一行注释,当key为null时会抛出空指针NullPointerException,这也说明了HashTabel是不允许Key为null的。
//throws NullPointerException if the {@code key} is {@code null}.
2.15.3 HashTable组成
/**
* The hash table data.
* 真正存放数据的数组
*/
private transient Entry<?,?>[] table;
/**
* The total number of entries in the hash table.
*/
private transient int count;
/**
* The table is rehashed when its size exceeds this threshold. (The
* value of this field is (int)(capacity * loadFactor).)
* 重新hash的阈值
* @serial
*/
private int threshold;
/**
* The load factor for the hashtable.
* @serial
*/
private float loadFactor;
HashTable中的元素存在Entry<?,?>[] table中,是一个Entry数组,Entry是存放的节点,每一个Entry是一个链表。
2.15.4 HashTable中的Entry
final int hash;
final K key;
V value;
Entry<K,V> next;
知道Entry是一个单链表即可,和HashMap中的Node结构相同,但是HashMap中还有Node的子类TreeNode。
2.15.5 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();
//在数组中的位置 0x7fffffff 是31位二进制1
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;
}
本质上就是先hash求索引,遍历该索引Entry链表,如果找到hash值和key都和put的key一样的时候就替换旧值,否则使用addEntry方法添加新值进入table,因为添加新元素就涉及到修改元素大小,还可能需要扩容等,具体看下边的addEntry方法可知。
private void addEntry(int hash, K key, V value, int index) {
Entry<?,?> tab[] = table;
//如果扩容需要重新计算hash,所以index和table都会被修改
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
//插入新元素
tab[index] = new Entry<>(hash, key, value, e);
count++;
modCount++;
}
tab[index] = new Entry<>(hash, key, value, e);
这行代码是真正插入新元素的方法,采用头插法,单链表一般都用头插法(快)。
2.15.6 get方法
@SuppressWarnings("unchecked")
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
get方法就简单很多就是hash,找到索引,遍历链表找到对应的value,没有则返回null。相比诸君都已经看到,HashTable中方法是用synchronized修饰的,所以其操作是线程安全的,但是效率会受影响。