一、摘要
在集合系列的第一章,咱们了解到,Map 的实现类有 HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties 等等。
本文主要从数据结构和算法层面,探讨 Hashtable 的实现,如果有理解不当之处,欢迎指正。
二、简介
Hashtable 一个元老级的集合类,早在 JDK 1.0 就诞生了,而 HashMap 诞生于 JDK 1.2,在实现上,HashMap 吸收了很多 Hashtable 的思想,虽然二者的底层数据结构都是 数组 + 链表 结构,具有查询、插入、删除快的特点,但是二者又有很多的不同。
打开 Hashtable 的源码可以看到,Hashtable 继承自 Dictionary,而 HashMap 继承自 AbstractMap。
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
.....
}
HashMap 继承自 AbstractMap,HashMap 类的定义如下:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
.....
}
其中 Dictionary 类是一个已经被废弃的类,翻译过来的意思是这个类已经过时,新的实现应该实现 Map 接口而不是扩展此类,这一点我们可以从它代码的注释中可以看到:
/**
* <strong>NOTE: This class is obsolete. New implementations should
* implement the Map interface, rather than extending this class.</strong>
*/
public abstract
class Dictionary<K,V> {
......
}
Hashtable 和 HashMap 的底层是以数组来存储,同时,在存储数据通过key
计算数组下标的时候,是以哈希算法为主,因此可能会产生哈希冲突的可能性。
通俗的说呢,就是不同的key
,在计算的时候,可能会产生相同的数组下标,这个时候,如何将两个对象放入一个数组中呢?
而解决哈希冲突的办法,有两种,一种开放地址方式(当发生 hash 冲突时,就继续以此继续寻找,直到找到没有冲突的hash值),另一种是拉链方式(将冲突的元素放入链表)。
Java Hashtable 采用的就是第二种方式,拉链法!
于是,当发生不同的key
通过一系列的哈希算法计算获取到相同的数组下标的时候,会将对象放入一个数组容器中,然后将对象以单向链表
的形式存储在同一个数组下标容器中,就像链子一样,挂在某个节点上,如下图:
与 HashMap 类似,Hashtable 也包括五个成员变量:
/**由Entry对象组成的数组*/
private transient Entry[] table;
/**Hashtable中Entry对象的个数*/
private transient int count;
/**Hashtable进行扩容的阈值*/
private int threshold;
/**负载因子,默认0.75*/
private float loadFactor;
/**记录修改的次数*/
private transient int modCount = 0;
具体各个变量含义如下:
- **table:**表示一个由 Entry 对象组成的链表数组,Entry 是一个单向链表,哈希表的
key-value
键值对都是存储在 Entry 数组中的; - **count:**表示 Hashtable 的大小,用于记录保存的键值对的数量;
- **threshold:**表示 Hashtable 的阈值,用于判断是否需要调整 Hashtable 的容量,threshold 等于
容量 * 加载因子
; - **loadFactor:**表示负载因子,默认为 0.75;
- **modCount:**表示记录 Hashtable 修改的次数,用来实现快速失败抛异常处理;
接着来看看Entry
这个内部类,Entry
用于存储链表数据,实现了Map.Entry
接口,本质是就是一个映射(键值对),源码如下:
private static class Entry<K,V> implements Map.Entry<K,V> {
/**hash值*/
final int hash;
/**key表示键*/
final K key;
/**value表示值*/
V value;
/**节点下一个元素*/
Entry<K,V> next;
......
}
我们再接着来看看 Hashtable 初始化过程,核心源码如下:
public Hashtable() {
this(11, 0.75f);
}
this 调用了自己的构造方法,核心源码如下:
public Hashtable(int initialCapacity, float loadFactor