Java:HashTable类小记
Java:HashTable类小记
对 Java 中的 HashTable类,做一个微不足道的小小小小记
概述
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
// ...
}
需要说明的是:HashTable 和 HashMap 的实现原理基本一样,差别无非是:
HashTable 不允许 key 和 value 为 null;
HashTable 是线程安全的。
但是 HashTable 线程安全的策略实现代价却太大了,简单粗暴,get/put 所有相关操作都是 synchronized 的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。
扩容方式稍有不同,见后续分析
对于 HashMap 的一点记录,见:Java:HashMap类小记
相同点
对比了 HashTable 与 HashMap 的相同点,如下:
- 都可以用来存储键值对
- 底层哈希表结构查询速度都很快
- 内部通过单链表解决冲突问题,容量不足会自动增加
- 都实现了 Map 接口
- 都实现了 Serializable 接口,支持序列化
- 实现了 Cloneable 接口,可以被克隆
不同点
HashTable 继承了 Dictionary 类,而 HashMap 是继承了 AbstractMap类。Dictionary 是任何可将键映射到相应值的类的抽象父类,而 AbstractMap 是基于 Map 接口的实现,它以最大限度地减少实现此接口所需的工作。
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
...
}
public abstract class AbstractMap<K,V> implements Map<K,V> {
...
} /************************************************/ public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
...
} public abstract
class Dictionary<K,V> {
...
}
据说这是因为:历史原因
线程安全不一样:Hashtable 是线程安全的,而 HashMap 不是线程安全的,但是我们也可以通过
Collections.synchronizedMap(hashMap)
,使其实现同步。//这是Hashtable的put()方法:
public synchronized V put(K key, V value){
...
} /************************************************/ //这是HashMap的put()方法:
public V put(K key, V value) {
...
}
从上面的源代码可以看到 Hashtable 的
put()
方法是synchronized的,而 HashMap 的put()
方法却不是。HashMap 的 key 和 value 都允许为 null,而 Hashtable 的 key 和 value 都不允许为 null。
HashMap 遇到 key 为 null 的时候,调用 putForNullKey 方法进行处理(JDK 8中在hash函数中做了判断),而对 value 没有处理;
Hashtable 遇到 null,直接返回
NullPointerException
。// HashMap
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
} // 在 hash(key)方法中:对于null做了特殊的处理
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
} /************************************************/ // Hashtable
public synchronized V put(K key, V value) {
// Make sure the value is not null
// 1.值为null直接抛出异常
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
// 由于key是null,null当然是没有hashCode()这种方法的
// 因此直接抛出异常NullPointerException
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
// ....
}
遍历方式的内部实现上不同:HashMap使用 Iterator 遍历,HashTable 使用Enumeration 遍历;
据说这也是因为:历史原因
关于 Iterator 与 Enumeration 的异同,见:Java:Iterator接口与fail-fast小记
HashMap 和 HashTable 的初始化方式和扩容方式不同:
HashMap:在构造函数中不创建数组,而是在第一次put时才创建,且初始大小为16;之后每次扩充容量为原来的两倍;
HashTable:在构造函数直接创建hashtable,且其初始大小为11,之后每次扩充2n+1
// hashmap初始化:HashMap hashMap = new HashMap();
// 默认构造函数
public HashMap() {
// 先不创建,在使用的时候再创建
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
// 其中:DEFAULT_LOAD_FACTOR = 0.75f;
// DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
} public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
} /************************************************/ // hashtable初始化:Hashtable hashtable = new Hashtable();
public Hashtable() {
// 构造函数直接创建hashtable
this(11, 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)
initialCapacity = 1;
this.loadFactor = loadFactor;
// 始化table,获得大小为initialCapacity的table数组
table = new Entry<?,?>[initialCapacity];
// 计算阀值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
} // hastable扩容:
public synchronized V put(K key, V value) {
// ... // 添加元素,见下面
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++;
} // rehash,扩容操作
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table; // overflow-conscious code
// 这里:新容量=旧容量 * 2 + 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;
}
// 新建一个size = newCapacity 的HashTable
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;
}
}
}
参考
https://blog.csdn.net/u010983881/article/details/49762595
https://mp.weixin.qq.com/s/q1r9Pno6ANUzZ9wMzA-JSg
Java:HashTable类小记的更多相关文章
- Java Hashtable类
哈希表(Hashtable)是原来的java.util中的一部分,是一个字典的具体实现. 然而,Java2重新设计的哈希表,以便它也实现了Map接口.因此,哈希表现已集成到集合框架.它类似于Has ...
- Java:ConcurrentHashMap类小记-1(概述)
Java:ConcurrentHashMap类小记-1(概述) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentHas ...
- Java:ConcurrentHashMap类小记-2(JDK7)
Java:ConcurrentHashMap类小记-2(JDK7) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentH ...
- Java:TreeMap类小记
Java:TreeMap类小记 对 Java 中的 TreeMap类,做一个微不足道的小小小小记 概述 前言:之前已经小小分析了一波 HashMap类.HashTable类.ConcurrentHas ...
- Java:ConcurrentHashMap类小记-3(JDK8)
Java:ConcurrentHashMap类小记-3(JDK8) 结构说明 // 所有数据都存在table中, 只有当第一次插入时才会被加载,扩容时总是以2的倍数进行 transient volat ...
- Java:HashMap类小记
Java:HashMap类小记 对 Java 中的 HashMap类,做一个微不足道的小小小小记 概述 HashMap:存储数据采用的哈希表结构,元素的存取顺序不能保证一致.由于要保证键的唯一.不重复 ...
- Java:LinkedHashMap类小记
Java:LinkedHashMap类小记 对 Java 中的 LinkedHashMap类,做一个微不足道的小小小小记 概述 public class LinkedHashMap<K,V> ...
- Java:LinkedList类小记
Java:LinkedList类小记 对 Java 中的 LinkedList类,做一个微不足道的小小小小记 概述 java.util.LinkedList 集合数据存储的结构是循环双向链表结构.方便 ...
- Java:ArrayList类小记
Java:ArrayList类小记 对 Java 中的 ArrayList类,做一个微不足道的小小小小记 概述 java.util.ArrayList 是大小可变的数组的实现,存储在内的数据称为元素. ...
随机推荐
- 2021.9.12周六PAT甲级考试复盘与总结
周六PAT甲级考试复盘与总结 先说结论:仍未步入"高手"行列:现在的学习节奏与方法是对的,有十万分的必要坚持下去. 题目 知识点 分数 T1 前缀和.二分 11 / 20 T2 排 ...
- AQS学习(二) AQS互斥模式与ReenterLock可重入锁原理解析
1. MyAQS介绍 在这个系列博客中,我们会参考着jdk的AbstractQueuedLongSynchronizer,从零开始自己动手实现一个AQS(MyAQS).通过模仿,自己造轮子来学习 ...
- vue el-transfer新增拖拽排序功能---sortablejs插件
<template> <!-- target-order="unshift"必须设置,如果不设置的话后台穿的value值得顺序会被data重置 - --> ...
- 手把手教你 Docker Compose安装DOClever
一.什么是Docker Compose以及Docker Compose的安装和使用 查看我的另外一篇博客:Docker Compose的安装和使用 二.DOClever是什么 DOClever是一个可 ...
- 阿里云短信功能php
1. 引入文件: https://help.aliyun.com/document_detail/53111.html?spm=a2c1g.8271268.10000.99.5a8ddf25gG0wW ...
- 2.docker安装及原理
一. docker的架构 1.1 docker的架构 先来看docker官网给出的docker架构图: 看官网,docker的架构描述: https://docs.docker.com/get-sta ...
- javascript/html 禁止图片缓存
更新图片, 如果图片的url没有改变, 刷新页面之后图片会使用缓存的图片 Solutions: * js改变图片链接 (添加get参数) // 假设当前这个图片的dom对象为img img.src + ...
- Css3 3D 旋转动画效果
需求: 1.一个列表滑动效果为360 旋转 准备: 1.css 基础 2.Css 动画基础animation 3.transform-style概念 4 transform 概念 5 JavaScri ...
- P5956-[POI2017]Podzielno【数学】
正题 题目链接:https://www.luogu.com.cn/problem/P5956 题目大意 \(B\)进制下,给出序列\(a\),\(a_i\)表示数字\(i\)有多少个.求一个最大的\( ...
- MyBatis切换至MyBatis-plus踩坑Invalid bound statement (not found):
部分情况可以参考https://blog.csdn.net/wwrzyy/article/details/86034458 我的问题出现的根本原因就是没有扫描到mapper的xml文件 因为MyBat ...