源码解读:Hashtable源码解析(JDK8)
时间:2020-03-08
本文章向大家介绍源码解读:Hashtable源码解析(JDK8),主要包括源码解读:Hashtable源码解析(JDK8)使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
目录
Hashtable源码分析(JDK8)
一、简述
注意是Hashtable不是HashTable(t为小写),这不是违背了驼峰定理了嘛?这是因为Hashtable是在Java1.0的时候创建的,而集合的统一规范命名是在后来的Java2开始约定的,而当时又发布了新的集合代替它,所以这个命名也一直使用到现在,所以Hashtable是一个过时的集合了,现在已经不推荐使用这个类,虽说Hashtable过时了,我们还是有必要分析一下它,以便对Java集合框架有一个整体的认知。
二、特性
- Hashtable是基于
哈希表
实现的,每个元素是一个key-value对,其内部也是通过拉链法解决冲突问题。 - Hashtable是
线程安全
的,键值不允许为null
。 - Hashtable实现了Serializable接口,它支持序列化,实现了Cloneable接口,能被克隆。
- Hashtable采用数组+链表存储元素,HashMap则是采用数组+链表/红黑树(JDK8)存储元素。
三、成员变量
//Entry类型数组
private transient Entry<?,?>[] table;
//元素数量
private transient int count;
//阈值,判断是否调整table长度
private int threshold;
//加载因子
private float loadFactor;
//集合修改次数
private transient int modCount = 0;
//序列化ID
private static final long serialVersionUID = 1421746759512286392L;
四、构造函数
//默认构造 默认容量11,加载因子0.75
public Hashtable() {
this(11, 0.75f);
}
//指定初始容量
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
//指定初始容量 与 加载因子 的构造函数
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) //初始容量为0
initialCapacity = 1;//指定初始容量为1
this.loadFactor = loadFactor;
table = new Entry<?,?>[initialCapacity]; //创建Entry类型数组
//确定阈值,(容量*阈值)或 (MAX_ARRAY_SIZE+1)之中小的值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
//根据另一个map创建table
public Hashtable(Map<? extends K, ? extends V> t) {
//初始容量 max(t的容量的2倍,11)
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);//将t中的元素加入table
}
- Hashtable默认容量是11,默认加载因子是0.75。
- 指定了初始容量和加载因子的构造函数,确认阈值要与MAX_ARRAY_SIZE+1比较。
- 利用map构造Hashtable,初始容量是map元素容量的2倍。
五、Entry节点类
//Entry类
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash; //哈希值
final K key; //键
V value; //值
Entry<K,V> next;//下一个entry
//构造函数
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
@SuppressWarnings("unchecked")
protected Object clone() {
//new Entry()
//next存在,调用clone()继续克隆
return new Entry<>(hash, key, value,
(next==null ? null : (Entry<K,V>) next.clone()));
}
// Map.Entry Ops
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
if (value == null)
throw new NullPointerException();
V oldValue = this.value;
this.value = value;
return oldValue;
}
//Entry equals
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
//key是否equals value是否equals
return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
(value==null ? e.getValue()==null : value.equals(e.getValue()));
}
//entry的hashcode hash与value的hashcode异或
public int hashCode() {
return hash ^ Objects.hashCode(value);
}
//输出key与value
public String toString() {
return key.toString()+"="+value.toString();
}
}
- 可以看出是个单链表。
- 节点类重写了
clone()
,可以使用clone()克隆整条链表,而HashMap的Entry类没有重写。
六、增/改
//添加元素(同步方法)
public synchronized V put(K key, V value) {
// Make sure the value is not null
//确保value不为空
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
//确保key在table中不存在
Entry<?,?> tab[] = table; //获取table
int hash = key.hashCode(); //计算key的hashcode(构建entry使用的hash)
int index = (hash & 0x7FFFFFFF) % tab.length; //保证hash值为正数,得到hash在table中的索引
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index]; //获取index位置的头节点
//遍历
for(; entry != null ; entry = entry.next) {
//如果entry的hash与entry key的hash都相等,说明键值对已经存在
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value; //获取旧值
entry.value = value; //替换value
return old; //返回旧值
}
}
//key在table中不存在
//插入
addEntry(hash, key, value, index);
return null;
}
//将集合t中的元素添加入talbe
public synchronized void putAll(Map<? extends K, ? extends V> t) {
for (Map.Entry<? extends K, ? extends V> e : t.entrySet()) //遍历map的entrySet
put(e.getKey(), e.getValue()); //put添加
}
- Hashtable中大部分方法都已经使用synchronized进行同步,所以保证线程安全。
- put()一开始判断value是否为空,保证value不为null。
key.hashCode()
当key为null时,抛出异常,保证key不为null。hash & 0x7FFFFFFF
int类型的任何值 &0x7FFFFFFF 都是一个正数,保证取模运算后,索引得到的是一个正数- 每次添加新元素,会根据计算出得index遍历链表,如果发现key已经存在,则替换并返回value,没有找到则添加新节点。
- putAll()采用遍历entrySet的方式,将每个entry添加入table。
//添加entry
private void addEntry(int hash, K key, V value, int index) {
modCount++;
//获取table
Entry<?,?> tab[] = table;
if (count >= threshold) { //元素数量 》= 阈值,扩容
// Rehash the table if the threshold is exceeded
rehash();
tab = table; //扩容后获取新table
hash = key.hashCode(); //计算entry的hash
index = (hash & 0x7FFFFFFF) % tab.length; //计算新的index
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index]; //获取index处的节点e
//将新元素放到index处,新节点的后继节点指向e
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
Hashtable将新节点插入链表时,采用的是头部插入,这与HashMap的尾部插入有所不同。
七、扩容(重点)
//扩容
@SuppressWarnings("unchecked")
protected void rehash() {
int oldCapacity = table.length; //获取旧的数组长度
Entry<?,?>[] oldMap = table; //获取talbe
// overflow-conscious code
//在应用数据分布在等差数据集合(如偶数)上时,如果公差与桶容量有公约数n,则至少有(n-1)/n数量的桶是利用 不到的。
//实际上 HashMap 也会有此问题,并且不能指定桶容量。所以 HashMap 会在取模哈希前先做一次哈希
int newCapacity = (oldCapacity << 1) + 1; //新数组容量是旧数组的2倍+1 保证数组长度是奇数
if (newCapacity - MAX_ARRAY_SIZE > 0) { // 新数组容量 》 最大容量
if (oldCapacity == MAX_ARRAY_SIZE) // 旧数组容量 = 最大容量,继续使用最大容量
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE; //新数组容量 = 最大容量
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; //创建新数组
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); //更新阈值
table = newMap; //table 指向 新数组
//从oldCapacity处向前遍历旧数组,将旧数组元素复制到新数组
for (int i = oldCapacity ; i-- > 0 ;) {
//遍历链表,移动链表上的元素
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old; //保存当前entry
old = old.next; //old指向下一个entry
int index = (e.hash & 0x7FFFFFFF) % newCapacity; //计算元素新的位置
//当前entry的后继节点 = index处的entry(上一个添加的entry)
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e; //index索引指向e
}
}
}
- 数组扩容时,新数组是旧数组的2倍+1,这样能至少能保证扩容后数组长度是奇数,这样做是为了让元素散列,减小哈希冲突概率。
- 数组最大长度是
Integer.MAX_VALUE - 8
,而不是Integer.MAX_VALUE
,即使MAX_ARRAY_SIZE依旧容量不足,也继续使用MAX_ARRAY_SIZE当作数组长度。 - HashMap中当
Integer.MAX_VALUE - 8
依旧容量不足时,可以扩容为Integer.MAX_VALUE
。 - 创建新数组后,重新计算每个元素的index,使用头部插入添加到新位置。
八、删
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table; //获取table
int hash = key.hashCode();//计算hashcode
int index = (hash & 0x7FFFFFFF) % tab.length; //根据hashcode计算索引
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index]; //获取index的头节点
//遍历链表
for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) { //entry的hash与key都相同
modCount++;
if (prev != null) { //前驱节点不为空
prev.next = e.next; //建立新连接
} else { //e是index的头节点
tab[index] = e.next;//更新index处entry
}
count--;
V oldValue = e.value;
e.value = null;
return oldValue; //返回e的value
}
}
return null; //没找到返回null
}
- 删除要先根据key的hashcode计算出元素在数组中的索引,再遍历链表,根据在链表中的位置进行删除。
- 删除结束返回节点value。
九、查
//根据key查找value
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) { //hash与key都相等
return (V)e.value;
}
}
return null;
}
//key存在返回value,不存在返回null
@Override
public synchronized V getOrDefault(Object key, V defaultValue) {
V result = get(key); //根据key获取value
//value不为空返回,为空返回null
return (null == result) ? defaultValue : result;
}
根据key计算出index,然后遍历链表,节点存在则返回value,不存在则返回null。
十、迭代(重要)
Hashtable提供了两种遍历方式:枚举器和迭代器。现在大部分集合框架都只采用迭代器遍历,这两者的区别,我会用另一篇文章讲解。
//Hashtable枚举类
//元素数量为0,返回“空枚举类”对象
private <T> Enumeration<T> getEnumeration(int type) {
if (count == 0) {
return Collections.emptyEnumeration(); //返回空枚举类
} else {
return new Enumerator<>(type, false); //返回Enumerator对象
}
}
//Hashtable迭代器
//元素数量为0,返回“空迭代器”对象
private <T> Iterator<T> getIterator(int type) {
if (count == 0) {
return Collections.emptyIterator(); //返回空迭代器
} else {
return new Enumerator<>(type, true); //返回迭代器对象
}
}
- 枚举器与迭代器的构建都使用了
Enumerator
类,唯一的区别是传入boolean值的不同,这是一个标志,用来确认构建的是枚举器还是迭代器。 - 当集合中没有元素时,一个返回空枚举器对象,一个返回空迭代器对象。
枚举类型
// Types of Enumerations/Iterations
// 枚举类型
private static final int KEYS = 0; //键
private static final int VALUES = 1; //值
private static final int ENTRIES = 2; //键值对
Enumerator
//提供了通过“elements()”和“keys()”遍历hashtable的接口
private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
Entry<?,?>[] table = Hashtable.this.table; //获取table
int index = table.length;//获取table长度
Entry<?,?> entry;
Entry<?,?> lastReturned;
int type;
//Enumerator是迭代器还是枚举类的标志
// true是迭代器遍历,false是枚举遍历
boolean iterator;
protected int expectedModCount = modCount;
Enumerator(int type, boolean iterator) {
this.type = type;
this.iterator = iterator;
}
//从后向前遍历table数组,直到找到不为null的entry
public boolean hasMoreElements() {
Entry<?,?> e = entry;
int i = index;
Entry<?,?>[] t = table;
/* Use locals for faster loop iteration */
while (e == null && i > 0) {
e = t[--i];
}
entry = e;
index = i;
return e != null;
}
//向下遍历链表
@SuppressWarnings("unchecked")
public T nextElement() {
Entry<?,?> et = entry;
int i = index;
Entry<?,?>[] t = table;
/* Use locals for faster loop iteration */
while (et == null && i > 0) {
et = t[--i];
}
entry = et;
index = i;
if (et != null) {
Entry<?,?> e = lastReturned = entry;
entry = e.next;
//根据type返回枚举类型
return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
}
throw new NoSuchElementException("Hashtable Enumerator");
}
// Iterator methods
// 判断是否存在下一个元素
public boolean hasNext() {
return hasMoreElements();
}
//获取下一个元素
public T next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
return nextElement();
}
//删除元素
public void remove() {
if (!iterator) //说明是枚举类,不能迭代
throw new UnsupportedOperationException();
if (lastReturned == null) //当前元素为空
throw new IllegalStateException("Hashtable Enumerator");
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
//遍历table
synchronized(Hashtable.this) {
Entry<?,?>[] tab = Hashtable.this.table;
int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
//遍历链表,找到entry,删除
for(Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
if (e == lastReturned) {
modCount++;
expectedModCount++;
if (prev == null)
tab[index] = e.next;
else
prev.next = e.next;
count--;
lastReturned = null;
return;
}
}
throw new ConcurrentModificationException();
}
}
}
- Enumerator类定义了一个
iterator
变量来确认构造的是枚举器还是迭代器。 - 枚举器使用
hasMoreElements()
与nextElement()
来遍历元素,一个方法用来定位位置,另一个方法用来返回元素。 - 枚举器无法删除元素,在
remove()
中,会首先判断iterator
,如果为false则抛出异常。
//返回“所有key”的枚举对象
public synchronized Enumeration<K> keys() {
return this.<K>getEnumeration(KEYS);
}
//返回“所有value”的枚举对象
public synchronized Enumeration<V> elements() {
return this.<V>getEnumeration(VALUES);
}
使用枚举器只能遍历Key集合与value集合,无法遍历key-value集合。
Iterator
private transient volatile Set<K> keySet; //key集合
private transient volatile Set<Map.Entry<K,V>> entrySet; //entry集合
private transient volatile Collection<V> values; //value集合
//获得keySet
public Set<K> keySet() {
if (keySet == null) //keySet为空
keySet = Collections.synchronizedSet(new KeySet(), this); //同步创建一个keySet
return keySet;//返回
}
//KeySet类
private class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return getIterator(KEYS); //返回一个key迭代器
}
}
//获得entrySet
public Set<Map.Entry<K,V>> entrySet() {
if (entrySet==null)
entrySet = Collections.synchronizedSet(new EntrySet(), this); //同步创建entrySet
return entrySet;
}
//EntrySet类
private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {
return getIterator(ENTRIES); //返回一个entry迭代器
}
}
//获得value类、集合
public Collection<V> values() {
if (values==null)
values = Collections.synchronizedCollection(new ValueCollection(),
this); //同步创建values
return values;
}
//ValueCollection类
private class ValueCollection extends AbstractCollection<V> {
public Iterator<V> iterator() {
return getIterator(VALUES); //返回一个value迭代器
}
}
可以看出,迭代器遍历,就是调用getIterator(int type)
返回一个迭代器,再使用hashNext()
、next()
、remove()
对集合进行遍历或删除操作。
原文地址:https://www.cnblogs.com/le-le/p/12442505.html
- Silverlight 2.0 beta1 堆栈
- 微信内置浏览器 长按识别二维码 功能的两三个坑与解决方案
- Android中EditText
- 比特币分叉了,这到底是怎么回事?
- Excel导入导出数据库01
- 一些移动 Web 前端开发上的要点记录
- Angularjs基础(十一)
- Silverlight 2 的基础XAML语法学习
- TextView显示html文件中的图片
- 继百度、阿里之后,农业也刮起人工智能风,看它们都干了些啥?
- Windows Server 2008 与 .NET Framework 的版本之间有什么关系
- asp.net mvc相关开源项目推荐
- Android监听来电和去电
- PostCSS 插件postcss-lazyimagecss:自动填写width / height 属性
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 用于NLP的Python:使用Keras进行深度学习文本生成
- 用Python的Numpy求解线性方程组
- python用于NLP的seq2seq模型实例:用Keras实现神经机器翻译
- 使用Python和Keras进行主成分分析、神经网络构建图像重建
- python使用Flask,Redis和Celery的异步任务
- 在R语言中进行缺失值填充:估算缺失值
- Docsify 如何添加目录列表
- Dubbo日志链路追踪TraceId选型
- 重温C++的设计思想
- 设计一个网站(域名)的镜像
- LoRa终端设备ASR6505普通GPIO操作
- LoRa终端设备ASR6505之I2C通信
- 我在暴躁同事小张的胁迫下学会了Go的交叉编译和条件编译
- LoRa终端设备ASR6505之PingPong通信
- LoRa点对点通信,OLED显示(内附代码)