介绍
- 实现Map接口(这意味着以键/值对的形式存储数据)
- 哈希表实现
- 不支持null值或null键
- Hashtable是同步的
- 无序的
- 具有fail-fast机制
- 默认初始容量11(尽量维持奇数)
- 默认加载因子.75f
继承关系 继承类介绍
Dictionary:虽然HashTable继承自Dictionary,但是此类已过时
Map:实现Map接口(这意味着以键/值对的形式存储数据)
变量解析(所有代码都不是完整的源码,而是精简过后的源码!结合源码食用更佳)
//存放bucket的数组
private transient Entry<?, ?>[] table;
//count表示table的实际元素数量
private transient int count;
扩容临界值 table的实际元素数量超过此值执行扩容
private int threshold;
//默认加载因子
private float loadFactor;
//结构被修改的次数
private transient int modCount = 0;
//table的最大容量大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
源码解析 常用方法解析
put(K key, V value)
作用:将指定值与指定键关联,如果此映射中已经包含键的映射,则替换旧值
//同步方法
public synchronized V put(K key, V value) {
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
//计算index
//hash & 0x7FFFFFFF可以将首位固定为0(0x7FFFFFFF = 0111 1111 1111 1111 1111 1111 1111 1111),结果就为整数
//length为奇数取模运算的结果更加均匀
int index = (hash & 0x7FFFFFFF) % tab.length;
Entry<K,V> entry = (Entry<K,V>)tab[index];
//该index位置的节点不为Null 则遍历该链表 如果有节点满足key与hash的条件 则直接替换value
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
//该index位置的节点为null,则不存在冲突 执行添加
addEntry(hash, key, value, index);
//添加到链表首部
private void addEntry(int hash, K key, V value, int index) {
//结构被修改
modCount++;
Entry<?,?> tab[] = table;
//判断实际容量是否超过扩容阈值 超过即需要扩容
if (count >= threshold) {
// 扩容
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
//将当前节点添加到链表的首部
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
//扩容
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
//原容量*2+1
int newCapacity = (oldCapacity << 1) + 1;
//创建新的数组
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
//计算扩容阈值 = 容量 * 加载因子
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
//遍历所有节点 重新计算index位置 存入新数组
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;
}
}
}
有点长总结一下put流程:
- index的计算公式是(hash & 0x7FFFFFFF) % tab.length
- key存在直接替换value
- 判断是否需要扩容,新容量为原容量的两倍+1(尽量得到奇数)
- 将新的节点添加到index对应的位置,如果该位置已经有节点存在,则新节点作为链表头部
remove(Object key)
作用:删除key及其对应的值
//同步方法
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
//根据计算的index 获取对应位置的节点
Entry<K,V> e = (Entry<K,V>)tab[index];
//该节点可能是由多个节点组成的链表 这个循环是为了遍历该链表的所有节点 取出与key对应的value返回
//且需要维护删除后链表的新顺序
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;
}
replace(K key, V value)
作用:替换指定键映射的值,如果键存在!相当于map.put(key, value)
//同步方法
public synchronized V replace(K key, V value) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
//和remove类似 它也是遍历index位置链表的所有节点 找到key对应的节点后替换value
//和remove不同的是它不需要维护新的链表顺序 故而更为简单一些
for (; e != null; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
return null;
}
get(Object key)
作用:返回指定键映射到的值
//同步方法
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
//和replace类似 这个就更简单了 它也是遍历index位置链表的所有节点 找到key对应的节点后直接返回value
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
扩展知识点
- HashTable和HashMap的区别(如果这两个源码你都看过了,你应该试着写出他们之间的区别,写完参看我下面的写法)
- HashMap默认初始容量为16,每次扩容为原容量2倍
- HashTable默认初始容量为11,每次扩容为原容量2倍+1
- HashTable尽量使用奇数作为容量,这样取模运算的结果会更加均匀,也就提高了遍历的效率
- HashMap使用2的幂作为容量,使用位运算来得到index,效率要高于HashTable的取模运算
- HashMap使用位运算取代HashTable的取模运算引入了新的问题,哈希分配不均匀,所以HashMap改进这一问题,加入扰动计算
- HashTable是线程安全的
- 当哈希冲突时,HashTable是将节点添加到链表首部,而HashMap是将节点添加到链表尾部
- 数据结构不同,HashMap使用数组+链表+红黑树,HashTable使用数组+链表,当链表长度超过8之后HashMap的迭代效率高于HashTable(已树形化)
- 继承关系不同,HashMap继承自AbstractMap,而HashTable继承自Dictionary