前言
书接上文,上一篇中介绍并分析了 ArrayDeque,本篇将对线程安全的 Map 接口最终实现类 Hashtable 进行介绍与分析。
由于 Hashtable 继承自 Dictionary 抽象类,而在 Dictionary 抽象类中涉及到了 Enumaration 接口的只是,所以在查看 Hashtable 源码之前还需要看一下 Enumeration 接口和 Dictionary 抽象类。
/**
* 一个实现了 Enumeration 接口的对象,产生一系列元素s,一次一个。连续调用 nextElement 方法返回这一系列的连续元素s。
*
* 比如,打印一个 Vector<E> v 中的所有元素s:
* <pre>
* for (Enumeration<E> e = v.elements(); e.hasMoreElements();)
* System.out.println(e.nextElement());</pre>
* <p>
*
* 方法s被提供用来通过一个 vector 中的元素s枚举,一个 hashtable 的键s,和一个 hashtable 中的值s。Enumeration 也可以用来指定一个输入到为到一个 SequenceInputStream。
*
* 注意:这个接口的功能与 Iterator 接口重复了。进一步说,Iterator 添加了一个可选择的 remove 操作,并且有更短的方法名s。新的实现类应该考虑优先使用 Iterator 而不是 Enumeration。
*/
public interface Enumeration<E> {
/**
* 测试当前 enumertion 是否包含更多元素s。
*/
boolean hasMoreElements();
/**
* 如果当前 enumeration 对象还有至少一个对象可以提供,返回这个 enumeration 的下一个对象。
*/
E nextElement();
}
通过 Enumeration 接口的源码可以看到,它的功能与 Iterator 接口基本类似,都是用来具体实现迭代操作的,但是 Enumeration 只能获取元素,而 Iterator 比它多了一个 remove 方法,并且 Iterator 中相同作用的方法名称更短含义更清晰,所以 Java doc 推荐使用 Iterator。
/**
* Dictionary 类是任何映射键s和值s的类的抽象父类(这里应该是后来没有改),比如 Hashtable 类。每个键和每个
* 值在一个对象中。在任何一个 Dictionary 对象中,每个键都关联到至多一个值。给定一个 Dictionay 和一个键,
* 关联元素可以(就可以被)查询。任何 non-null 对象都可以被用来作为一个键和一个值。
*
* 作为一个规定,equals 方法应该被这个类的实现类用来决定两个键s是否相同。
*
* 注意:这个类是过时的。新的实现类应该实现 Map 接口,而不是继承自这个类。
*/
public abstract
class Dictionary<K,V> {
/**
* 唯一构造器。(通常用来隐式的被子类构造器s调用。)
*/
public Dictionary() {
}
/**
* 返回当前 dictionary 中条目s(唯一键s)的数量。
*/
abstract public int size();
/**
* 测试当前 dictionary 是否没有键s映射到值。对于 isEmpty 方法的通常规约是当且仅当这个 dictionary
* 不包含任何条目s才返回 true。
*/
abstract public boolean isEmpty();
/**
* 返回当前 dictionary 中的一个键s的枚举类型集合。对于 keys 方法的通常规约是返回一个生成当前
* dictionary 包含的所有的条目s的键s的 Enumeration 对象。
*/
abstract public Enumeration<K> keys();
/**
* 返回当前 dictionary 中的一个值s的枚举类型集合。对于 elements 方法的通常规约是返回一个生成当前
* dictionary 中所有元素s包含在条目s中的一个 Enumeratin 对象。
*/
abstract public Enumeration<V> elements();
/**
* 返回当前 dictionary 中这个键映射到的值。isEmpty 方法的通常规约是如果当前 dictionary 包含对于指定
* 键的一个条目,关联的值被返回,不然的话,返回 null。
*/
abstract public V get(Object key);
/**
* 在当前 dictionary 中映射指定键到指定值。键和值都不能为 null。
*
* 如果这个 dictionary 已经包含了一个指定键的条目,这个键返回的值已经在当前 dictionary 中,修改条目
* 来包含新的元素。如果当前 dictionary 还没有一个对应于指定键的条目,一个对于指定键和值的条目将被创建,
* 并且返回 null。
*
* 值可以被通过使用一个与原始键 equal 的键调用 get 方法返回。
*/
abstract public V put(K key, V value);
/**
* 从当前 dictionary 中移除键(还有它的对应值)。如果键不在当前 dictionary 中则不做任何事。
*/
abstract public V remove(Object key);
}
需要注意的是在 Dictionary 类中有两个方法 #keys 和 #elements,返回的是键s和条目s的枚举类型对象集合,了解了 Dictionary 抽象类之后就可以查看 Hashtable 的源码了。
/**
* 这个类实现了一个 hash table,意味着映射键s到值s。任何非 null 的对象可以被用来作为一个键或者一个值。
*
* 为了成功地存储和从一个 hashtable 中遍历对象s,作为键s使用的对象s必须实现 hashCode 方法和 equals 方
* 法。
*
* 一个 Hastable 实例有两个影响其性能的参数:初始化容量和加载因子。容量是在这个 hash table 中的桶的数量,
* 初始化容量是这个 hash table 被创建时的容量。注意 这个 hash table 是开放的:发生 "hash 碰撞" 时,一个
* 单桶存储了多个条目s,必须被按照顺序搜索。加载因子是一个这个 hash table 在它的容量被自动扩容之前允许到达
* 多满的计量值。初始化容量和加载因子参数s仅仅是对实现类的暗示。具体的 rehash 方法子啊何时和如何被调用的细节
* s是实现以来的。
*
* 通常的,默认加载因子 (.75) 提供了一个时间与空间话费的一个权衡。更高的值s减少了空间消耗但是提高了在查询一
* 个条目(这将影响大部分 Hashtable 操作s,包括 get 和 put)时的时间消耗。
*
* 这个初始化容量控制一个为了 rehash 操作s的空间消耗和时间消耗的权衡,是时间消费的。如果初始化容量大于这个
* Hashtable 将包含的最大条目数量除以加载因子的值,rehash 操作s将不会发生。但是,甚至过高的初始化容量会浪
* 费空间。
*
* 如果在一个 Hashtable 中将要创建很多条目,通过一个足够大的容量而不是通过让它在需要扩容 table 时自动扩容
* 来建立它可以允许条目s以更高效的方式被插入。
*
* 这个例子建立了一个 numbers 的 hashtable。它使用 numbers 的名称s作为键s:
* <pre> {@code
* Hashtable<String, Integer> numbers
* = new Hashtable<String, Integer>();
* numbers.put("one", 1);
* numbers.put("two", 2);
* numbers.put("three", 3);}</pre>
*
* <p>为了获得一个 number,使用下面的代码:
* <pre> {@code
* Integer n = numbers.get("two");
* if (n != null) {
* System.out.println("two = " + n);
* }}</pre>
*
* 通过 iterator 方法返回的 通过所有这个类的 “collection 视图方法s” 返回的 collections 迭代器s是
* fail-fast 的:如果这个 Hashtable 在迭代器被创建后的任意时间点被结构性的改动了,以除了通过迭代器自身
* remove 方法的方式,这个迭代器将抛出一个 {@link ConcurrentModificationException}。因此,因此,对于
* 并发修改,迭代器失败的快速与干净,而不是在未来某个不确定的时间点冒险做任何不确定的行为。Hashtable 的键s和
* 元素 elements 方法s返回的枚举对象s不是 fail-fast 的。
*
* 注意一个迭代器的 fail-fast 行为不能提供像它所描述的那样的保证,一般来说,不可能对于不同步的并发操作做任何
* 硬性的保证。基于最好性能的基础,fail-fast 迭代器抛出一个 ConcurrentModificationException。因此,编
* 写一个依赖于这个异常来保证其正确性的程序是错误的:迭代器的 fail-fast 行为应该只被用于检查 bugs。
*
* Java Collections 框架。不像新的 collection 实现类s,{@code Hashtable} 是线程安全的。如果不需要一
* 个线程安全的实现类,建议使用 {@link HashMap} 来替代 {@code Hashtable}。如果对于一个线程安全的实现类
* 有很高的高并发需要,那么建议使用 {@link java.util.concurrent.ConcurrentHashMap} 来代替 {@code
* Hashtable}。
*
* Hashtable 继承自 Dictionary,实现了 Map,Cloneable 和 Serializable 接口
*/
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
/**
* 这个 hash table 的数据。
*/
private transient Entry<?,?>[] table;
/**
* 这个 hash table 中条目的总数。
*/
private transient int count;
/**
* 当它的长度超过它的阈值时,这个 table 将被 rehashed。(这个类属性的值是 (int)(capacity *
* loadFactor))
*/
private int threshold;
/**
* 这个 hashtable 的加载因子。
*/
private float loadFactor;
/**
* 当前 Hashtable 已经被结构性变更的次数。结构性变更s是那些改变了当前 Hashtable 中条目数量或者改变了
* 它的内部结构(比如rehash)的操作。这个类属性被用来使 Hashtable 的 Collection-views fail-
* fast。(查看 ConcurrentModificationException)。
*/
private transient int modCount = 0;
private static final long serialVersionUID = 1421746759512286392L;
/**
* 使用指定的初始化容量和指定加载因子构造一个新的,空的 hashtable。
*/
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];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
/**
* 使用指定初始化容量和默认加载因子(0.75)来构造一个新的,空的 hashtable。
*/
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* 使用一个默认初始化容量(11)和加载因子(0.75)来构造一个新的,空的 hashtable。这里和 HashMap 的默
* 认初始化容量不同。
*/
public Hashtable() {
this(11, 0.75f);
}
/**
* 使用于指定 Map 相同的映射s构造一个新的 hashtable。这个 hashtable 被使用一个足够装下指定 Map
* 中映射s的初始化容量和一个默认加载因子(0.75)创建。
*/
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
/**
* 返回当前 hashtable 中的键s数量。
*/
public synchronized int size() {
return count;
}
/**
* 测试这个 hashtable 是否没有任何键s映射到值s。
*/
public synchronized boolean isEmpty() {
return count == 0;
}
/**
* 返回当前 hashtable 中键s的枚举对象集合。
*/
public synchronized Enumeration<K> keys() {
return this.<K>getEnumeration(KEYS);
}
/**
* 返回当前 hashtable 中值s的枚举对象集合。
*/
public synchronized Enumeration<V> elements() {
return this.<V>getEnumeration(VALUES);
}
/**
* 判断是否一些键在当前 hashtable 中映射到了指定值。
*
* 这个操作比 {@link #containsKey containsKey} 方法更昂贵。
*
* 注意这个方法在功能上与 {@link #containsValue containsValue} 相同,(它是 collections 框架
* 中 {@link Map} 接口的一部分)。
*
* 这里的处理与 HashMap 完全相同
*/
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
Entry<?,?> tab[] = table;
for (int i = tab.length ; i-- > 0 ;) {
//循环数组
for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
//遍历 Node 链下的所有 Nodes
if (e.value.equals(value)) {
return true;
}
}
}
return false;
}
/**
* 如果当前 hashtable 中有一个或者多个键s映射到这个值。
*
* 注意这个方法与 {@link#contains contains} 是等价的(早于 {@link Map} 接口)。
*/
public boolean containsValue(Object value) {
return contains(value);
}
/**
* 测试指定对象是否是一个当前 hashtable 中键。
*/
public synchronized boolean containsKey(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
/**
* 这里 0x7FFFFFFF 是 int 类型的最大值(64 位,首位为 0,其余位都为 1),它计算条目在数组中
* 的位置的方式也与 HashMap 中的不同。
**/
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 true;
}
}
return false;
}
/**
* 返回指定键映射到的值,或者如果当前 map 不包含这样的键则返回 {@code null}。
*
* 更一般地说,如果当前 map 的一个键 {@code k} 到一个值 {@code v} 存在 {@code
* (key.equals(k))} 的关系,那么当前方法返回 {@code v},不然的话它返回 {@code null}。(至多值
* 能有一个这样的映射。
*/
@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;
}
/**
* 可以分配的数组的最大长度。一些 VMs 保留一些头部字可能会导致 OutOfMemoryError:需要的数组长度超
* 过了 VM 限制。
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
*
* 增加当前 hashtable 的容量并在内部组织此哈希表,为了更高效的容纳以及访问它的条目s。这个方法在
* hashtable 中的键s超过它的容量和加载因子时被调用。
*/
@SuppressWarnings("unchecked")
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1; // oldCapacity * 2 + 1
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// 继续使用 MAX_ARRAY_SIZE 桶s
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++; //这里递增了 modCount,注意 HashMap resize 的时候并不会递增
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); //阈值
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next; //步移
//重新分配
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {
// 如果超过了阈值,Rehash 当前 table
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// 创建新条目
@SuppressWarnings("unchecked")