1. 简介
Hashtable 是基于哈希表来实现的,每个元素是一个 key-value 形式,内部是数组+链表的数据结构,通过单链表解决 hash 冲突的问题,容量不足的时候也会自动进行扩容操作,存储的数据是无序的;Hashtable 的 JDK1.0 引入的类,基本已经被废弃使用;Hashtable 的重要方法使用 synchronized 关键字修饰,是线程安全的;key 和 value 都不可以为 null,否则将会抛出 NullPointerException 异常。
与 HashMap 相似,底层采用数组+链表的数据结构,根据 key 找到相应的桶,相同的 key 通过链表维护,当数组桶达到阈值后也会进行动态的扩容,但是 Hashtable 不会转换为红黑树。默认大小为 11
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {}
- 实现了 Map 接口,提供了键值对的增删改查等基础操作
- 继承了 Directory 字典类
- 实现了 Cloneable 接口,支持拷贝
- 实现了 Serializable 接口,支持序列化和反序列化
Hashtable 和 HashMap 不同点:
- 线程安全方面:Hashtable 线程安全、HashMap 线程不安全
- 底层数据结构:Hashtable 数组 + 链表、HashMap 数组 + 链表 + 红黑树
- 默认初始容量:Hashtable 是 11、HashMap 是 16(2 的次幂)
- 扩容大小:Hashtable 原来大小的2倍+1、HashMap 是原来大小的 1.5 倍数
- Hashtable 不允许 key 和 value 为空、HashMap 可以允许一个 key 为null,任意 value 为 null
2. 成员变量
/**
* 使用 Entry 数组存储键值对对象,Entry 实际为单向链表的表头
*/
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).)
* 扩容阈值,当超过这个值后会进行扩容操作,计算方式:数组容量 * 加载因子
* @serial
*/
private int threshold;
/**
* The load factor for the hashtable.
* 加载因子
* @serial
*/
private float loadFactor;
/**
* Hashtable bucket collision list entry
*/
private static class Entry<K,V> implements Map.Entry<K,V> {
final 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;
}
...
}
3. 构造方法
这里的构造方法不像 HashMap 中的懒加载方式,而是在构造对象的时候就先创建一个 hash 表放在那里
/**
* Constructs a new, empty hashtable with the specified initial
* capacity and the specified load factor.
*
* @param initialCapacity the initial capacity of the hashtable.
* @param loadFactor the load factor of the hashtable.
* @exception IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive.
*/
// 用指定的容量大小和加载因子构造一个新的哈希表
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 对象,创建大小为 initialCapacity 的数组
table = new Entry<?,?>[initialCapacity];
// 计算阈值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
/**
* Constructs a new, empty hashtable with the specified initial capacity
* and default load factor (0.75).
*
* @param initialCapacity the initial capacity of the hashtable.
* @exception IllegalArgumentException if the initial capacity is less
* than zero.
*/
// 指定初始容量,使用默认加载因子构造一个空的哈希表
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* Constructs a new, empty hashtable with a default initial capacity (11)
* and load factor (0.75).
无参构造方法,容量为 11 ,加载因子为 0.75
*/
public Hashtable() {
this(11, 0.75f);
}
/**
* Constructs a new hashtable with the same mappings as the given
* Map. The hashtable is created with an initial capacity sufficient to
* hold the mappings in the given Map and a default load factor (0.75).
*
* @param t the map whose mappings are to be placed in this map.
* @throws NullPointerException if the specified map is null.
* @since 1.2
*/
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
4. put 方法
// 线程安全
public synchronized V put(K key, V value) {
// Make sure the value is not null
// 确保 value 不为空,如果为 null 抛出空指针异常
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
// 得到下标,先对 hash 值取整然后取余 hash&0x7FFFFFF 为了保证结果为正数
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
// 如果存在相同的 key 则遍历链表把那个 key 对应的 value 换成新的 value
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
// 迭代 index 索引位置,如果该位置处的链表中存在一样的key,则替换 value 返回旧值
V old = entry.value;
entry.value = value;
return old;
}
}
// 如果上面循环结束了还是没有在表中找打相同 hash 和 key 的值,那就调用该方法进行插入
addEntry(hash, key, value, index);
return null;
}
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = 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")
// 直接构建结点,放到table中,作为链表的头节点
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
小总结:
- 对 value 做非空判断,hashtable 不允许 key、value 为空
- 根据 key 的 hashCode 找到对应的索引
- 如果存在相同的 key 则遍历链表把那个 key 对应的 value 换成新的 value
- 判断表大小是否超出阈值,如果是调用 rehash 方法扩容
- 直接将键值对插入到 table数组中,作为链表的头节点
5. rehash 方法
/**
* Increases the capacity of and internally reorganizes this
* hashtable, in order to accommodate and access its entries more
* efficiently. This method is called automatically when the
* number of keys in the hashtable exceeds this hashtable's capacity
* and load factor.
增加哈希表的容量并在内部重组该哈希表,以便更有效地容纳和访问其条目。
当哈希表中的键数超过此哈希表的容量和负载因子时,将自动调用此方法。
*/
@SuppressWarnings("unchecked")
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
// 新容量 = 旧容量的 * 2 + 1
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;
}
// 初始化一个新的 hash表,size 为 newCapacity
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
// 计算新的阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
// 遍历旧的表将元素重新计算 下标放入到新的 HashTable 中
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;
}
}
}
小总结
在向集合中添加元素的时候调用 addEntry 首先会进行容量检验判断,查看当前数组大小是否超过阈值(count >= threshold)如果是就会调用 rehash 方法进行扩容。
- 通过对就数组值左移1位+1 得到新的数组的容量,即(旧容量 * 2 + 1)
- 判断是否达到最大容量
- 创建一个新容量大小的数组
- 通过 数组容量 * 负载因子得到新的阈值
- 循环遍历旧的表,对每一个元素的key重新 hash 然后放入新表的相应位置
6. get 方法
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key.equals(k))},
* then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
*
* @param key the key whose associated value is to be returned
* @return the value to which the specified key is mapped, or
* {@code null} if this map contains no mapping for the key
* @throws NullPointerException if the specified key is null
* @see #put(Object, Object)
*/
@SuppressWarnings("unchecked")
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
// 计算 key 的 hash 值
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;
}
小总结:
- 计算 key 的 hash 值
- 计算下标位置
- 迭代链表,匹配找到相应 key 的 value并返回,未找到则返回 null
7. remove 方法
/**
* Removes the key (and its corresponding value) from this
* hashtable. This method does nothing if the key is not in the hashtable.
* 从此哈希表中删除键(及其对应的值)。 如果键不在哈希表中,则此方法不执行任何操作。
* @param key the key that needs to be removed
* @return the value to which the key had been mapped in this hashtable,
* or <code>null</code> if the key did not have a mapping
* @throws NullPointerException if the key is <code>null</code>
*/
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
// 根据 key 计算下标
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
// 判断 e != null,表示下标的位置是有元素的
// 遍历链表,找到 key 相等的节点,该表前后链表关系,将 value 置为空等待 GC 回收
for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
V oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
8. 常用方法
-
boolean isEmpty()
功能:测试此哈希表是否没有键映射到值
public class test { public static void main(String[] args) { Hashtable<Object, Object> hashtable = new Hashtable<>(); System.out.println(hashtable.isEmpty()); } } 运行结果: true -
Object put(Object key, Object value)
功能:将指定 key 映射到此哈希表中的指定 value
public class test { public static void main(String[] args) { Hashtable<Object, Object> hashtable = new Hashtable<>(); hashtable.put(1,"aaa"); System.out.println(hashtable.toString()); //String toString():返回此 Hashtable 对象的字符串表示形式, } } 运行结果: {1=aaa} -
int size()
功能:返回此哈希表中的键的数量
public class test { public static void main(String[] args) { Hashtable<Object, Object> hashtable = new Hashtable<>(); hashtable.put(1,"aaa"); System.out.println(hashtable.size()); } } 运行结果: 1 -
Enumeration elements(),Enumeration keys()
功能:Enumeration elements() 返回此哈希表中的值的枚举
功能:Enumeration keys() 返回此哈希表中的键的枚举
public class test { public static void main(String[] args) { Hashtable<Object, Object> hashtable = new Hashtable<>(); hashtable.put(1,"aaa"); hashtable.put(2,"bbb"); Enumeration<Object> elements = hashtable.elements(); while (elements.hasMoreElements()){ System.out.println(elements.nextElement()); } } } 运行结果: bbb aaa public class test { public static void main(String[] args) { Hashtable<Object, Object> hashtable = new Hashtable<>(); hashtable.put(1,"aaa"); hashtable.put(2,"bbb"); hashtable.put(3,"ccc"); Enumeration<Object> keys = hashtable.keys(); while (keys.hasMoreElements()){ System.out.println(keys.nextElement()); } } } 运行结果: 3 2 1 -
Object get(Object key)
功能:返回指定键所映射到的值,如果此映射不包含此键的映射,则返回 null
public class test { public static void main(String[] args) { Hashtable<Object, Object> hashtable = new Hashtable<>(); hashtable.put(1,"aaa"); hashtable.put(2,"bbb"); System.out.println(hashtable.get(1)); System.out.println(hashtable.get(3)); } } 运行结果: aaa null -
contains()
- boolean contains(Object value):测试此映射表中是否存在与指定值关联的键
- boolean containsKey(Object key):测试指定对象是否为哈希表中的值
- boolean containsValue(Object value):如果此Hashtable 将一个或多个键映射到此值,则返回true
public class test { public static void main(String[] args) { Hashtable<Object, Object> hashtable = new Hashtable<>(); hashtable.put(1,"aaa"); hashtable.put(2,"bbb"); hashtable.put(2,"aaa"); System.out.println(hashtable.contains("aaa")); System.out.println(hashtable.containsKey(2)); System.out.println(hashtable.containsValue("aaa")); } } 运行结果: true true true -
Object remove(Object key)
功能:从哈希表中移除该键及其相应的值
public class test { public static void main(String[] args) { Hashtable<Object, Object> hashtable = new Hashtable<>(); hashtable.put(1,"aaa"); hashtable.put(2,"bbb"); hashtable.put(3,"ccc"); System.out.println(hashtable.toString()); hashtable.remove(3); System.out.println(hashtable.toString()); } } 运行结果: {3=ccc, 2=bbb, 1=aaa} {2=bbb, 1=aaa} -
Object clone()
功能:创建此哈希表的副本
public class test { public static void main(String[] args) { Hashtable<Object, Object> hashtable = new Hashtable<>(); hashtable.put(1,"aaa"); hashtable.put(2,"bbb"); hashtable.put(3,"ccc"); Object clone = hashtable.clone(); System.out.println(hashtable.toString()); System.out.println(clone.toString()); } } 运行结果: {3=ccc, 2=bbb, 1=aaa} {3=ccc, 2=bbb, 1=aaa} -
void clear()
功能:将此哈希表清空,使其不包含任何键
public class test { public static void main(String[] args) { Hashtable<Object, Object> hashtable = new Hashtable<>(); hashtable.put(1,"aaa"); hashtable.put(2,"bbb"); hashtable.put(3,"ccc"); System.out.println(hashtable.toString()); hashtable.clear(); System.out.println(hashtable.toString()); } } 运行结果: {3=ccc, 2=bbb, 1=aaa} {}
参考文献: