目录

1. 是什么

线程安全的hashmap

2. 如何使用

public class HashtableTest
{
public static void main(String[] args) throws InterruptedException
{
Hashtable<Integer, Integer> map = new Hashtable<>();
Thread thread1 = new Thread(()->{
for (int i = 0; i < 100000; i++)
{
map.put(i, i);
}
}); Thread thread2 = new Thread(()->{
for (int i = 100000; i < 200000; i++)
{
map.put(i, i);
}
}); thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(map);
System.out.println(map.size());
for (int i = 0; i < 200000; i++)
{
if (!map.contains(i))
{
throw new RuntimeException("并发put有问题");//不会抛出异常说明并发put没问题
}
System.out.println(map.remove(i));
}
}
}

3. 原理分析

3.1. uml

可克隆,可序列化,实现了Map接口

3.2. 构造方法

使用链地址法(单链表)解决Hash冲突

初始化容量为11,默认的加载因子为0.75

public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable { //使用Entry数组实现
private transient Entry<?,?>[] table; //map中实际元素的个数
private transient int count; //以下两个决定了什么时候扩容
private int threshold;
private float loadFactor; private transient int modCount = 0; public Hashtable() {
//初始化容量为11,加载因子为0.75
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数组
table = new Entry<?,?>[initialCapacity];
//threshold取int MAX_ARRAY_SIZE = Integer.MAX_VALUE 8和initialCapacity * loadFactor中的小者
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
}

3.3. put方法

//加了synchronized
public synchronized V put(K key, V value) {
// Make sure the value is not null
//与HashMap不同,这里value不能为null
if (value == null) {
throw new NullPointerException();
} // 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) {
//找到了,替换value
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
} //没有找到,新建节点加入链表
addEntry(hash, key, value, index);
return null;
}

3.3.1. 使用synchronized加锁

public synchronized V put(K key, V value) {
//...
}

3.3.2. 计算key落在entry数组中的哪个位置【或者说哪个链表】

Entry<?,?> tab[] = table;
int hash = key.hashCode();
//计算下标
//计算index是通过对数组长度取模而不是使用与操作
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];

3.3.3. 遍历链表直到找到相等的节点或者到末尾

//遍历链表直到找到相等的节点或者到末尾
for(; entry != null ; entry = entry.next) {
//找到了,替换value
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}

3.3.4. 没有找到,新建节点加入链表头部

  • addEntry方法
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++;
}
3.3.4.1. 扩容
  • rehash方法
@SuppressWarnings("unchecked")
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table; // overflowconscious code
//新capacity=旧capacity*2+1
int newCapacity = (oldCapacity << 1) + 1;
//如果容量超过了MAX_ARRAY_SIZE(int MAX_ARRAY_SIZE = Integer.MAX_VALUE 8),那么以MAX_ARRAY_SIZE为准
if (newCapacity MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
//以新capacity创建新数组
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
//由后往前遍历entry数组
for (int i = oldCapacity ; i > 0 ;) {
//从头到尾遍历链表
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
//e表示此次要迁移的节点,old表示下一个要迁移的节点
Entry<K,V> e = old;
old = old.next; //计算e在新entry数组中的位置
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
//把e的next指向新entry数组中的位置(链表的头节点)
e.next = (Entry<K,V>)newMap[index];
//再把新entry数组中的位置赋值为e
//等于就是头插法
newMap[index] = e;
}
}
}

3.4. get方法

//加了synchronized
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;
}

3.4.1. 使用synchronized加锁

public synchronized V get(Object key) {
}

3.4.2. 计算key落在entry数组中的哪个位置【或者说哪个链表】

Entry<?,?> tab[] = table;
int hash = key.hashCode();
//计算在那个链表中
int index = (hash & 0x7FFFFFFF) % tab.length;

3.4.3. 遍历链表直到找到相等的节点或者到末尾

//遍历链表找到相等的节点
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}

3.5. remove方法

使用synchronized修饰

同get方法找到节点,删除的操作就是链表节点的删除操作

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];
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;
//help GC
V oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}

3.5.1. 使用synchronized加锁

public synchronized V remove(Object key) {
}

3.5.2. 计算key落在entry数组中的哪个位置【或者说哪个链表】

Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];

3.5.3. 遍历链表直到找到相等的节点或者到末尾,把value置为null

@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
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;
//help GC
V oldValue = e.value;
e.value = null;
return oldValue;
}
}

3.6. containsKey方法

//加了synchronized
public synchronized boolean containsKey(Object key) {
//以下逻辑同get方法
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 true;
}
}
return false;
}

3.6.1. 使用synchronized加锁

public synchronized boolean containsKey(Object key) {
}

3.6.2. 计算key落在entry数组中的哪个位置【或者说哪个链表】

Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

3.6.3. 遍历链表直到找到相等的节点或者到末尾

for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return true;
}
}
return false;

11.Java SDK源码分析系列笔记-Hashtable的更多相关文章

  1. spring源码分析系列 (8) FactoryBean工厂类机制

    更多文章点击--spring源码分析系列 1.FactoryBean设计目的以及使用 2.FactoryBean工厂类机制运行机制分析 1.FactoryBean设计目的以及使用 FactoryBea ...

  2. Spring mvc源码分析系列--Servlet的前世今生

    Spring mvc源码分析系列--Servlet的前世今生 概述 上一篇文章Spring mvc源码分析系列--前言挖了坑,但是由于最近需求繁忙,一直没有时间填坑.今天暂且来填一个小坑,这篇文章我们 ...

  3. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  4. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

  5. MyCat源码分析系列之——BufferPool与缓存机制

    更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...

  6. jquery2源码分析系列

    学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...

  7. [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat

    概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...

  8. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

  9. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  10. spring源码分析系列

    spring源码分析系列 (1) spring拓展接口BeanFactoryPostProcessor.BeanDefinitionRegistryPostProcessor spring源码分析系列 ...

随机推荐

  1. 在 CentOS 系统下搭建 ZeroTier Moon 服务器

    安装 ZeroTier One: 首先,确保已经安装了 ZeroTier One.你可以按照上述说明,使用以下命令进行安装: sudo yum install zerotier-one 启动 Zero ...

  2. Linux终端居然也可以做文件浏览器?

    大家好,我是良许. 在抖音上做直播已经整整 5 个月了,我很自豪我一路坚持到了现在[笑脸] 最近我在做直播的时候,也开始学习鱼皮大佬,直播写代码.当然我不懂 Java 后端,因此就写写自己擅长的 Sh ...

  3. [开源] 分享一个自己开发的, 整合SMS/Mail/Telegram/微信四个平台的开源信息收发平台

    起因于已有的聚合信息发送平台无法满足自己的需求. 不支持我需要的平台,或不支持接收信息后进行处理,或不放心把涉及隐私的消息通过第三方平台发送 利用SMS发送短信(上一篇文章中分享的开源项目) 利用SM ...

  4. redis那些数据类型?分别在那些场景使用

    (1)string 这是最基本的类型了,没啥可说的,就是普通的set和get,做简单的kv缓存 例子:常规计数:微博数,粉丝数等 (2)hash 这个是类似map的一种结构,这个一般就是可以将结构化的 ...

  5. Java 21 新特性

    Java 21 是 Java 语言的一次重要更新,引入了若干新的特性,提升了开发者的编程效率和代码质量.本文将详细介绍 Java 21 的新特性,包括基础概念.使用方法.常见实践以及最佳实践. 简介 ...

  6. python,获取当前日期且以当前日期为名称创建文件名

    爬虫爬取信息时,需要把爬取的内容存到txt文档中,且爬虫是每天执行,以日期命名能避免出现名称重复等问题,解决方法如下 import time import os import sys path = o ...

  7. Linux 给用户 赋某个文件夹操作的权限(实现三权分立)

    Linux 给用户 赋某个文件夹操作的权限 这里用的ubuntu16.04 一.配置网站管理员 linux文件或目录的权限分为,读.写.可执行三种权限.文件访问的用户类别分为,文件创建者.与文件创建者 ...

  8. linux系统权限管理

    一.认识linux系统的文件权限 首先随便在一个目录下使用ls -l(可简写为ll)指令,就会把该目录下所有的文件和目录的权限显示出来,例如,在根目录下使用ls -l: (深蓝字:目录,白字:文件,浅 ...

  9. 29.1K star!免费接入GPT-4/DeepSeek等顶级大模型,这个开源API神器绝了!

    嗨,大家好,我是小华同学,关注我们获得"最新.最全.最优质"开源项目和高效工作学习方法 还在为天价API费用发愁?这个开源项目让你免费畅用GPT-4.DeepSeek.Claude ...

  10. 记一次docker buildx build 推送到本地私有仓库出现 connection refused 的问题

    想在本地编译多个架构的基础镜像,这样后续有其他业务使用的时候,不必从头开始编译. 使用传统的 docker build -t ImageName:tag 方式,只能编译和主机相同架构的镜像. 而doc ...