HashTable
属性成员
//hash表数组
private transient Entry<?,?>[] table;
//元素个数
private transient int count;
//扩容阈值
private int threshold;
//负载因子
private float loadFactor;
//改动次数
private transient int modCount = 0;
构造函数
/** 无参构造函数
构造一个具有默认初始容量 (11) 和负载因子 (0.75) 的空哈希表。
*/
public Hashtable() {
this(11, 0.75f);
}
/**
使用指定的初始容量和默认加载因子 (0.75) 构造一个新的空哈希表。
@param initialCapacity 哈希表的初始容量。
@exception IllegalArgumentException 如果初始容量小于零抛出异常
*/
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
使用指定的初始容量和指定的负载因子构造一个新的空哈希表。
@param initialCapacity 哈希表的初始容量。
@param loadFactor 哈希表的加载因子。
@exception IllegalArgumentException 如果初始容量小于零,或者负载因子为非正数。
*/
public Hashtable(int initialCapacity, float loadFactor) {
//如果指定容量小于0,不用存了都报错
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//如果负载因子小于0,或者loadFactor是非数字的数
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
//如果初始容量为空,则设置为1
if (initialCapacity==0)
initialCapacity = 1;
//负载因子赋值
this.loadFactor = loadFactor;
//初始化table
table = new Entry<?,?>[initialCapacity];
//扩容阈值赋值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
/**
构造一个与给定 Map 具有相同映射关系的新哈希表。哈希表的初始容量足以容纳给定 Map 中的映射和默认负载因子 (0.75)。
@param 其映射将放置在此映射中的映射。如果指定的映射为空,则@throws NullPointerException。
*/
public Hashtable(Map<? extends K, ? extends V> t) {
//设置容量为传入map的两倍,如果还小于默认容量就是用默认容量11
this(Math.max(2*t.size(), 11), 0.75f);
//将传入哈希表t放入table里
putAll(t);
}
hash值和index计算
hash值
- HashTable中的hash值计算直接使用了
Object的hashcode()
方法计算 - HashMap(1.8中):
// 1. 取hashCode值: h = key.hashCode()
// 2. 高位参与低位的运算:h ^ (h >>> 16)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
index
- HashTable中的index下标(对应位桶数组的下标)直接用hash值对表长取余
(hash & 0x7FFFFFFF) % tab.length;
- HashMap(1.8)的index计算:将对哈希码扰动处理后的结果 与运算(&) (数组长度-1),最终得到存储在数组table的位置(即数组下标、索引)
h & (length-1);
核心方法
线程安全
HashTable在核心方法前加了synchronized关键字,表示为同步方法,以达到线程同步的目的,不过这样的方法,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
put方法
public synchronized
V put(K key, V value):存入键值对key-value 键和值都不能是 null
/**
存入键值对key-value 键和值都不能是 null。
可以通过使用与原始键相同的键调用 get 方法来检索该值。
@param key 哈希表键 @
param value 值
@return 指定键在此哈希表中的前一个值,或者 null 如果它没有一个 @exception NullPointerException 如果键或值是 null @see Objectequals(Object) @see get(Object)
*/
public synchronized V put(K key, V value) {
//确保该值不为空
if (value == null) {
throw new NullPointerException();
}
//创建tab数组指向table
Entry<?,?> tab[] = table;
//计算hash值
int hash = key.hashCode();
//key对应的位桶下标(hash值对表长度取余)
int index = (hash & 0x7FFFFFFF) % tab.length;
//创建entry指向对应位置的位桶头结点
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
//遍历链表
for(; entry != null ; entry = entry.next) {
//判断结点的hash值和key是否与传入的相等
if ((entry.hash == hash) && entry.key.equals(key)) {
//如果相等 保存旧值
V old = entry.value;
//赋值新值
entry.value = value;
//返回旧值
return old;
}
}
//如果没有找到key相等的结点,调用addEntry方法将结点添加进表里
addEntry(hash, key, value, index);
//返回空
return null;
}
private void addEntry(int hash, K key, V value, int index) {
//改动次数+1
modCount++;
//创建指针保存table地址
Entry<?,?> tab[] = table;
//如果元素个数>扩容阈值
if (count >= threshold) {
// 如果超过阈值,则重新散列表(扩容)
rehash();
//指向新表
tab = table;
//重新计算hash值
hash = key.hashCode();
//计算位置
index = (hash & 0x7FFFFFFF) % tab.length;
}
// 创建新结点
//位桶数组index位置
//创建辅助指针e指向头结点
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
//头插入 结点放入桶中,新结点的next指针指向e(原头结点) 完成头插
tab[index] = new Entry<>(hash, key, value, e);
//元素个数+1
count++;
}
resize()扩容方法
protected void rehash() {
//保存旧容量
int oldCapacity = table.length;
//oldMap保存旧表
Entry<?,?>[] oldMap = table;
// 新容量为旧容量的两倍+1
int newCapacity = (oldCapacity << 1) + 1;
//如果新容量>integer的最大值-8
if (newCapacity - MAX_ARRAY_SIZE > 0) {
//如果旧容量为integer的最大值-8
if (oldCapacity == MAX_ARRAY_SIZE)
// 使用旧容量继续跑
return;
//旧容量设置为integer的最大值-8
newCapacity = MAX_ARRAY_SIZE;
}
//创建容量为newCapacity的hash表
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
//改动次数+
modCount++;
//重新计算扩容阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
//table指向新表
table = newMap;
//遍历旧表进行数据转移
for (int i = oldCapacity ; i-- > 0 ;) {
//从最后一个桶开始遍历,old指向链表头结点
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
//辅助指针e先指向头结点
Entry<K,V> e = old;
//old后移
old = old.next;
//计算新位置
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
//e的next指向新表对应位置的头结点
e.next = (Entry<K,V>)newMap[index];
//头插法 (e已经连接了原头结点)
newMap[index] = e;
}
}
}
get方法
public synchronized
V get(Object key):返回指定键映射到的值,如果此映射不包含键的映射,则返回 null
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) {
//如果找到hash值和key与传入的相等的则返回对应的值
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
//找不到返回空
return null;
}
remove方法
public synchronized
V remove(Object key):从此哈希表中删除key(及其相应的value)。如果key不在哈希表中,则此方法不执行任何操作。
/**
@param key 需要删除的键
@return 键在此哈希表中映射到的值,或者null 如果键没有映射
*/
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
//计算hash值
int hash = key.hashCode();
//计算位置
int index = (hash & 0x7FFFFFFF) % tab.length;
//辅助指针指向头结点
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
//prev记录前一个结点,e为当前结点
//遍历桶
for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
//如果hash值和key和结点对应相等
if ((e.hash == hash) && e.key.equals(key)) {
//改动次数+1
modCount++;
if (prev != null) {
//前一个结点指针当前结点的后一个结点(越过当前结点,相当于删除)
prev.next = e.next;
} else {
//如果是头结点为要删除的结点,将桶头结点设置为头结点的下一个结点
tab[index] = e.next;
}
//元素个数-1
count--;
//保存旧值
V oldValue = e.value;
//将e的值设为空
e.value = null;
//返回旧值
return oldValue;
}
}
//如果没找到对应的 返回空
return null;
}
HashMap和Hashtable的区别
-
HashMap是非线程安全的,Hashtable是线程安全的,因为使用了
synchronized
关键字来保证线程安全。 -
HashMap
允许key和value都为null,而Hashtable
都不能为null
。 -
Hashtable和HashMap扩容的方法不一样,Hashtable中数组默认大小11,扩容方式是 old*2+1。HashMap中数组的默认大小是16,而且一定是2的指数,增加为原来的2倍。
-
两者通过hash值散列到hash表的算法不一样,Hashtable是古老的除留余数法,直接使用Object的hashcode,而后者是强制容量为2的幂,重新根据hashcode计算hash值,在使用hash和(hash表长度 – 1)进行与运算,也等价取膜,但更加高效,取得的位置更加分散,偶数,奇数保证了都会分散到。前者就不能保证。