HashTable源码学习
在之前学习HashMap的时候,我们知道在JDK8中HashMap是采用Node数组+链表+红黑树的方式实现的。那么HashTable又是怎样的?
字面上看应该还是利用的Hash表来处理的。那么我们基于这样一点知识来学习一下HashTable的设计和实现过程。
从结构上看,HashTable的结构也没有HashMap那么复杂。所以实现起来应该还是比较简单吧。从类的静态变量看,基本和HashMap没有什么差别。
比如table、threshold、loadFactor,似乎说明HashTable也仅仅是HashMap的小弟弟。那么是这样吗?咋从初始化方法看起。
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 = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
public Hashtable() {
this(11, 0.75f);
}
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
在初始化方法中,和HashMap类似有4个方法。当传入的初始化容量为0,那么就就初始化为1,否则如果是正常的数字,那么就直接做为容量了。看来是和HashMap不一样哦。然后计算了扩容上限。在默认的重载因子还是和hashMap一样0.75,如果传入的是一个map,那么就把容量的2倍与11做比较,然后取最大值。这里为什么要选择11而不是12呐?而且看的出来Hashtable相比与HashMap还是相当的保守呀。
那么在添加元素的时候,hashtable又是如何做的呐?是否和hashmap一样?
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); }
// Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; //采用传入的key的hashCode作为hash值,这和hashMap的二次计算有所不同 int hash = key.hashCode(); //和hashMap的下标计算类似 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; } } //如果没有找到的就添加元素 addEntry(hash, key, value, index); return null; } private void addEntry(int hash, K key, V value, int index) { modCount++;
Entry<?,?> tab[] = table; if (count >= threshold) { //扩容 // Rehash the table if the threshold is exceeded rehash();
tab = table; hash = key.hashCode(); //扩容之后计算下标 index = (hash & 0x7FFFFFFF) % tab.length; }
// Creates the new entry. @SuppressWarnings("unchecked") //拿到数组的下标,然后进行赋值 Entry<K,V> e = (Entry<K,V>) tab[index]; tab[index] = new Entry<>(hash, key, value, e); //对容量进行添加 count++; }
我们看到添加的方法用了synchronized修饰,说明是线程安全的。那么这里的扩容也显然是安全的。那么它是如何进行扩容的?
protected void rehash() {
//拿到容量
int oldCapacity = table.length;
//缓存老的数据
Entry<?,?>[] oldMap = table;
// overflow-conscious code
//用老的容量左移1位,然后加1
int newCapacity = (oldCapacity << 1) + 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;
//从后向前遍历
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;
}
}
}
可以看到这里的reshash就是对扩容,然后通过hash值与0x7FFFFFFF的与运算,然后得到新的地址空间。感觉这块还是比较绕的。但是感觉经过扩容之后就没有链表了。但好像上边也没有往链表中放数据。
在方法putIfAbsent中
@Override
public synchronized V putIfAbsent(K key, V value) {
Objects.requireNonNull(value);
// Makes sure the key is not already in the hashtable.
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;
if (old == null) {
//对链表赋值
entry.value = value;
}
return old;
}
}
//添加元素
addEntry(hash, key, value, index);
return null;
}
那么获取元素怎么?
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)) {
return (V)e.value;
}
}
return null;
}
获取元素的时候也是加的synchronized,那么也是线程安全的。首先计算下标,然后遍历链表。
- 小希的迷宫(并查集)
- Is It A Tree?(并查集)
- Constructing Roads(最小生成树)
- More is better(并查集)
- 快来给这个图表起个名字吧~
- Prim
- 仿经济学人——矩阵气泡图
- 左手用R右手Python系列——模拟登陆教务系统
- R语言数据清洗实战——复杂数据结构与list解析
- R语言爬虫实战——知乎live课程数据爬取实战
- Python爬虫系列(二)Quotes to Scrape(谚语网站的爬取实战)
- R语言数据清洗实战——世界濒危遗产地数据爬取案例
- Leetcode-Easy 437. Path Sum III
- R语言爬虫实战——网易云课堂数据分析课程板块数据爬取
- 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 数组属性和方法
- 设计模式学习(六)-抽象工厂模式
- 彻底完美解决安卓苹果手机点击输入框网页页面自动放大缩小
- 第22天:NLP实战(六)——基于PaddleHub的疫情期间网民情绪识别
- Echarts大数据可视化物流航向省份流向迁徙动态图,开发全解+完美参数注释
- Linux 常用操作以及概念
- GCD梳理与总结——封装
- 原生JS在网页上复制的所有文字后面自动加上一段版权声明
- 使用宝塔docker安装为知笔记私有部署
- 第4天:美团点评2020校招测试方向笔试试卷分析
- Element-UI饿了么时间组件控件按月份周日期,开始时间结束时间范围限制参数
- 微信小程序flex布局
- 细数 TS 中那些奇怪的符号
- 安装RabbitMQ无法访问localhost:15672的管理界面解决
- koa中http服务与websocket服务共享端口
- 第23天:NLP实战(七)——中文新闻主题分类