JDK1.8源码阅读Hashtable.class
在看hashtable前你需要了解
Hashtable本质是一条线性的链表:
0、Hashtable是较早的JDK版本中的类,数据结构是`Entry<K, V> next`单向链表
1、哈希函数(直接寻址法、数字分析法、平方取中法、折叠法、随机数法、除留余数法)
2、hashCode()的生成在Object类内置了方法
3、链表Entry<?, ?>[]的数据结构
4、如何插入和删除一个Entry
5、与HashMap的区别
祖先Dictionary(继承)
继承Dictionary<K,V>,本是抽象的key、value。内部全是抽象变量。
public abstract
class Dictionary<K,V> {
public Dictionary() { }
abstract public int size();
abstract public boolean isEmpty();
abstract public Enumeration<K> keys();
abstract public Enumeration<V> elements();
abstract public V get(Object key);
abstract public V put(K key, V value);
abstract public V remove(Object key);
}
兄弟Map,Cloneable,Serializable(实现接口)
Cloneable和Serializable都是一个空接口。
- Map<K, V>接口:定义变量的常规操作方法
- Cloneable接口:clone是首先分配内存,分配的内存与调用clone方法对象的内存相同,然后将源对象中各个变量的值,填充到新的对象中,填充完成后,clone方法返回一个新的地址,这个新地址的对象与源对象相同,只是地址不同。
- Serializable接口:赋予序列化能力
孩子hash、key、value
- 从数据结构可以看出Hashtable是由Entry<K, V>组成的单向链表
private static class Entry<K, V> implements Map.Entry<K, V> {
final int hash;//使用key的hashCode(),是Object类的native的方法
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;
}
- 其它常量表述的含义
private transient Entry<?, ?>[] table;//看1108行的数据结构
private transient int count;//统计Entry的数量
private int threshold;//addEntry的时候如果比count小,则调用rehash()增加容量,重新计算每个键值对的hashCode
private float loadFactor;
private transient int modCount = 0;
private static final long serialVersionUID = 1421746759512286392L;
- table:键值对数组,每个Entry本质上是一个单向链表的表头
- count:当前表中的Entry数量,如果超过了阈值,就会扩容,即调用rehash方法
- threshold:rehash阈值
- loadFactor:负载因子
- modCount :用来实现"fail-fast"机制的(也就是快速失败)。所谓快速失败就是在并发集合中,其进行迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你(你已经出错了)。
- serialVersionUID:版本序列号,序列化用的
究竟如何插入一个值
- 确保key不在table,如果key对应的值存在,则用新值覆盖value
- 如果key对应的值不存在,就调用addEntry方法加入到index位置
public synchronized V put(K key, V value) {
if (value == null) {
throw new NullPointerException();
}
//确保key不在table,如果key对应的值存在,则用新值覆盖value
Entry<?, ?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K, V> entry = (Entry<K, V>) tab[index];
for (; entry != null; entry = entry.next) {
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;
}
- tab[index]和HashMap不同,Hashtable选择把新插入的元素放到链表最前边
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?, ?> tab[] = table;//重新增加一个数组
if (count >= threshold) {
rehash();// 当前元素大于等于阈值,就扩容并且再计算hash值
tab = table;
hash = key.hashCode();
//0x7FFFFFFF 0111 1111 1111 1111 1111 1111 1111 1111
index = (hash & 0x7FFFFFFF) % tab.length;//第一位是0表示正数,0与任何数都是0,1与任何数都是1,因此以此保证第一位是0,其后不变
}
@SuppressWarnings("unchecked")
Entry<K, V> e = (Entry<K, V>) tab[index];// Creates the new entry.
//和HashMap不同,Hashtable选择把新插入的元素放到链表最前边,而且没有使用红黑树
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
究竟删除插入一个值
- e是要删除的节点,遍历链表,如果表中
(e.hash == hash) && e.key.equals(key)
则表示存在相同的节点,将其删除(前一个节点的next指向下一个节点)
public synchronized V remove(Object key) {
Entry<?, ?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K, V> e = (Entry<K, V>) tab[index];//e是要删除的节点
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;
}
你应该知道的
Hashtable的key/value为什么不可为null
也许Hashtable类的设计者当时认为null作为key 和value 是没有什么用的。
HashMap对象的key、value值均可为null。
HashTable对象的key、value值均不可为null。
Hashtable比HashMap出生早
Hashtable命名没用驼峰规则,HashMap用上了。Hashtable现在项目使用频率很低。
参考:https://blog.csdn.net/u010297957/article/details/51974340