1.定义
HashTable在java中的定义如下:
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
可以看出HashTable继承Dictionary类,实现Map接口。其中Dictionary类是可以将任何键映射到值的类的抽象父类。每个键和每个值都是一个对象。
HashTable定义了几个重要的参数:table,count,threhold,loadFactor
table:为一个Entry[]数组类型,Entry代表了节点,每一个Entry代表一个键值对。
count:HashTable的大小,指的是他包含Entry键值对的数量。
threshold:HashTable的阈值
loadFactor:加载因子
2.构造方法
public Hashtable() {
this(11, 0.75f);
}
默认构造函数,容量为11,加载因子为0.75
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
用指定初始容量和默认的加载因子(0.75)构造一个新的空哈希表。
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数组
table = new Entry[initialCapacity];
//计算阀值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
//初始化HashSeed值
initHashSeedAsNeeded(initialCapacity);
}
指定初始容量和指定加载因子,initHashSeedAsNeeded方法用于初始化hashSeed参数,其用于计算key的hash值,它与key的hashCode进行按位异或运算,hashSeed随机值主要是为了解决hash冲突。
3.主要方法
public synchronized V put(K key, V value) {
// 确保value不为null
if (value == null) {
throw new NullPointerException();
}
/*
* 确保key在table[]是不重复的
* 处理过程:
* 1、计算key的hash值,确认在table[]中的索引位置
* 2、迭代index索引位置,如果该位置处的链表中存在一个一样的key,则替换其value,返回旧值
*/
Entry tab[] = table;
int hash = hash(key); //计算key的hash值
int index = (hash & 0x7FFFFFFF) % tab.length; //确认该key的索引位置
//迭代,寻找该key,替换
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();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// 在索引位置处插入一个新的节点
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
//容器中元素+1
count++;
return null;
}
由上可知,根据key的hash值,根据hash值获得key在table数组中的索引位置,然后迭代该key处的Entry链表,如果该链表存在一个这样的key对象,那么直接替换value值即可,否则就讲该key-value节点插入到节点处。
public synchronized V get(Object key) {
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)) {
return e.value;
}
}
return null;
}
get方法比较简单,计算key的hash值,判断在table数组中的索引位置。
HashTable的put方法中的扩容操作
HashTable中的扩容操作
protected void rehash() {
int oldCapacity = table.length;
//元素
Entry<K,V>[] oldMap = table;
//新容量=旧容量 * 2 + 1
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
return;
newCapacity = MAX_ARRAY_SIZE;
}
//新建一个size = newCapacity 的HashTable
Entry<K,V>[] newMap = new Entry[];
modCount++;
//重新计算阀值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
//重新计算hashSeed
boolean rehash = initHashSeedAsNeeded(newCapacity);
table = newMap;
//将原来的元素拷贝到新的HashTable中
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;
}
}
}
rehash()是将原来的容量扩大两倍 + 1,同时需要将原来的HashTable中的元素复制到新的HashTable中。
4.HashTable和HashMap的区别
1.HashTable是基于Dictionary类,而HashMap是基于AbstractMap.
2.HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable的key和value都不能为null.
3.HashTable的方法是同步的,而HashMap不是,所以一般涉及多线程都推荐用HashTable。
网友评论