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 是大小可变的数组的实现,存储在内的数据称为元素. ...
随机推荐
- java版gRPC实战之一:用proto生成代码
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- 性能测试必备命令(4)- pstree
性能测试必备的 Linux 命令系列,可以看下面链接的文章哦 https://www.cnblogs.com/poloyy/category/1819490.html 介绍 显示进程树 语法格式 ps ...
- Windows安装Docker & Docker-Compose & 配置docker私有仓库
一定要给windows先创建软连接,不然系统盘会爆表的: mklink /j .docker D:\Administrator\.docker Win7安装Docker Dockerfile # FR ...
- scrum项目冲刺_day06总结
摘要:今日完成任务. 1.服务器部署完成 2.由于将数据库放到了服务器上,搜索功能需要修改 总任务: 一.appUI页面(已完成) 二.首页功能: 1.图像识别功能(已完成) 2.语音识别功能(已完成 ...
- 【PHP数据结构】插入类排序:简单插入、希尔排序
总算进入我们的排序相关算法的学习了.相信不管是系统学习过的还是没有系统学习过算法的朋友都会听说过许多非常出名的排序算法,当然,我们今天入门的内容并不是直接先从最常见的那个算法说起,而是按照一定的规则一 ...
- Docker系列(19)- 数据卷之Dockerfile
初识Dockerfile Dockerfile就是用来构建docker镜像的构建文件!命令脚本! 通过这个脚本生成镜像,镜像是一层一层的,脚本与一个个的命令,每个命令都是一层! # 创建一个docke ...
- Docker系列(17)- MySQL同步数据
#获取镜像 [root@localhost ~]# docker pull mysql:5.7 #启动容器,需要做数据挂载!安装启动mysql,需要配置密码的,这是注意点! #官方安装文档:docke ...
- Linux C语言 取得MTU (最大传输单元)
参照这篇博客: http://www.geekpage.jp/programming/linux-network/book/04/4-21.php * 查看主机当前网卡,哪块在使用. ifconfig ...
- Composer基础
摘要 本文介绍Composer的入门知识,包括require和autoload部分. Java有Maven, Node.js有npm, ROR有gem, 这些语言的程序员在开心地使用包管理工具加速开发 ...
- 『GoLang』字典Map
map是一种元素对的无序集合,一组称为元素value,另一组为唯一键索引key. 未初始化map的值为nil.map 是引用类型,可以使用如下声明: var map1 map[keytype]valu ...