热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JDK源码学习之HashTable(附带面试题)的学习笔记

本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。


【学习笔记】JDK源码学习之HashTable(附带面试题)

其他好文:


【学习笔记】JDK源码学习之LinkedHashMap(附带面试题
【学习笔记】JDK源码学习之HashMap(附带面试题)
【学习笔记】JDK源码学习之Vector(附带面试题)
【学习笔记】JDK源码学习之LinkedList(附带面试题)
【学习笔记】JDK源码学习之ArrayList(附带面试题)

什么是 HashTable ?它的数据类型是什么?和 HashMap 有什么关系?又和 HashMap 有什么区别?

老样子,老铁们系好安全带!发车—(深入了解 HashTable )。



由于 HashTable 现在使用的并不多,本文并没有长篇大论,只有干货!!!



1、什么是HashTable?

HashtableHashMap 一样是一个集合,不过不同于 HashMap 的是,HashMap 允许 null 键与 null 值,而 Hashtable 不允许,HashMap 是线程不安全的,而Hashtable 是线程安全的,由于线程安全性问题,HashMap 相对于 Hashtable 效率会更高一些。



🌹 HashTable 的使用:


Hashtable<Object, Object> hashtable &#61; new Hashtable<>();

通过上述我们能知道 HashTableKV 的数据结构。那接下来让我们看看其的底层源码吧。

源码&#xff1a;

public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
...
}

继承图&#xff1a;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lg7Nh9iO-1671711517725)(/Users/tiejiaxiaobao/Library/Application Support/typora-user-images/image-20221222115615229.png)]

在这里&#xff0c;我们看到 Hashtable 继承自 Dictionary 这个字典类&#xff0c;并实现了 Map、Cloneable和Serializable 接口&#xff0c;具体的这些接口作用以及 Map 接口里的方法已在之前的 HashMap 解析中介绍过&#xff0c;没看过的同学可以参考上面的推荐文章嗷。下面着重看下Dictionary这个抽象类中的方法。

Dictionary&#xff1a;

public abstract
class Dictionary<K,V> {
/**
* 一个空的构造方法
*/

public Dictionary() {
}
/**
* 一个用于计算长度的抽象方法
*/

abstract public int size();
/**
* 一个用于判断是否为空的抽象方法
*/

abstract public boolean isEmpty();
/**
* 一个用于取出key的抽象方法&#xff0c;取出类型为枚举
*/

abstract public Enumeration<K> keys();
/**
* 一个用于取出value的抽象方法&#xff0c;取出类型为枚举
*/

abstract public Enumeration<V> elements();
/**
* 根据key值取出value
*/

abstract public V get(Object key);
/**
* 以key对应value的形式存放值
*/

abstract public V put(K key, V value);
/**
* 根据key值移除某个元素
*/

abstract public V remove(Object key);
}

我们可以通过看上面的注释和方法来理解这个 Dictionary 。因为其是 抽象类 除了构造函数都是抽象方法&#xff0c;当 HashTable 继承 Dictionary 的时候则需要实现其的所有 抽象方法


2、HashTable中常用的变量、构造函数和方法


2.1 HashTalbe 中常用的变量

源码&#xff1a;

/**
* The hash table data.
*/

private transient Entry<?,?>[] table;
/**
* The total number of entries in the hash table.
*/

private transient int count;
/**
* The table is rehashed when its size exceeds this threshold. (The
* value of this field is (int)(capacity * loadFactor).)
*
* &#64;serial
*/

private int threshold;
/**
* The load factor for the hashtable.
*
* &#64;serial
*/

private float loadFactor;
/**
* The number of times this Hashtable has been structurally modified
* Structural modifications are those that change the number of entries in
* the Hashtable or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the Hashtable fail-fast. (See ConcurrentModificationException).
*/

private transient int modCount &#61; 0;
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID &#61; 1421746759512286392L;

  • table &#xff1a;存储数据的table数组。
  • count &#xff1a;Hashtable中元素的总数。
  • threshold &#xff1a;阈值。
  • loadFactor &#xff1a;加载因子。
  • modCount &#xff1a;修改次数。
  • serialVersionUID &#xff1a;版本ID号。

2.2 HashTable 中的构造函数

源码&#xff1a;

/**
* Constructs a new, empty hashtable with the specified initial
* capacity and the specified load factor.
*
* &#64;param initialCapacity the initial capacity of the hashtable.
* &#64;param loadFactor the load factor of the hashtable.
* &#64;exception IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive.
*/

// inialCapacity 为初始值
public Hashtable(int initialCapacity, float loadFactor) {
// 判断是否小于零&#xff0c;如果小于零则抛出 IllegalArgumentException 异常。
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "&#43;
initialCapacity);
// 判断负载因子是否合法&#xff0c;如果不合法则抛出异常
if (loadFactor <&#61; 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "&#43;loadFactor);
// 如果传入的初始值为0&#xff0c;则自动设置为1
if (initialCapacity&#61;&#61;0)
initialCapacity &#61; 1;
this.loadFactor &#61; loadFactor;
table &#61; new Entry<?,?>[initialCapacity];
// 设置阀值
threshold &#61; (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE &#43; 1);
}
/**
* Constructs a new, empty hashtable with the specified initial capacity
* and default load factor (0.75).
*
* &#64;param initialCapacity the initial capacity of the hashtable.
* &#64;exception IllegalArgumentException if the initial capacity is less
* than zero.
*/

// 定义初始值&#xff0c;因子为0.75
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* Constructs a new, empty hashtable with a default initial capacity (11)
* and load factor (0.75).
*/

// 设置初始容量为11&#xff0c;负载因子为0.75
public Hashtable() {
this(11, 0.75f);
}
/**
* Constructs a new hashtable with the same mappings as the given
* Map. The hashtable is created with an initial capacity sufficient to
* hold the mappings in the given Map and a default load factor (0.75).
*
* &#64;param t the map whose mappings are to be placed in this map.
* &#64;throws NullPointerException if the specified map is null.
* &#64;since 1.2
*/

public Hashtable(Map<? extends K, ? extends V> t) {
// 比较两倍的Map长度和11谁大&#xff0c;不免不必要的空间浪费
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}

通过源码我们能知道 HashTable 有四种构造函数&#xff1a;


  • Hashtable()
  • Hashtable(Map t)
  • Hashtable(int initialCapacity)
  • public Hashtable(int initialCapacity, float loadFactor)

2.3 HashTable 中常用的方法



♣️ 2.3.1 方法


源码&#xff1a;

// 加锁
public synchronized V put(K key, V value) {
// Make sure the value is not null
// 如果value为null&#xff0c;则抛出 NullPointerException 异常
if (value &#61;&#61; null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] &#61; table;
// 这里注意如果key空则也会抛出异常
int hash &#61; key.hashCode();
// 寻址法来进行寻找地址
int index &#61; (hash & 0x7FFFFFFF) % tab.length;
&#64;SuppressWarnings("unchecked")
Entry<K,V> entry &#61; (Entry<K,V>)tab[index];
// 遍历table[index]所连接的链表&#xff0c;查找是否已经存在key与需要插入的key值相同的节点&#xff0c;如果存在则直接更新value&#xff0c;并返回旧的value。
for(; entry !&#61; null ; entry &#61; entry.next) {
if ((entry.hash &#61;&#61; hash) && entry.key.equals(key)) {
V old &#61; entry.value;
entry.value &#61; value;
return old;
}
}
// 如果table[index]所连接的链表上不存在相同的key,则通过addEntry()方法将新节点加载链表的开头。
addEntry(hash, key, value, index);
return null;
}

通过上述代码我们能知道 HashTable 有一下几个特点&#xff1a;


  • 因为加了 synchronized &#xff0c;所以是线程安全的&#xff0c;但是效率是比较低的。
  • 源码中首先判断了 value 是否为 null 是否为空&#xff0c;如果为空则就抛出 NullPointerException 异常。同时也使用了 key.hashCode() &#xff0c;如果 keynull 的时候则也会抛出异常。这就是为什么HashTable中KV不能为null的原因。
  • 在使用 put() 方法时&#xff0c;首先会先循环判断是否已经存在 key &#xff0c;如果存在则直接替换&#xff0c;反之添加。

补充 addEntry 方法

private void addEntry(int hash, K key, V value, int index) {
modCount&#43;&#43;; // 操作次数加一
Entry<?,?> tab[] &#61; table;
// 首先判断容量是否充足&#xff0c;如果不充足则扩容
if (count >&#61; threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab &#61; table;
hash &#61; key.hashCode();
index &#61; (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
&#64;SuppressWarnings("unchecked")
// 创建新的entry
Entry<K,V> e &#61; (Entry<K,V>) tab[index];
// 放入数组中
tab[index] &#61; new Entry<>(hash, key, value, e);
// hashtable长度加1
count&#43;&#43;;
}


♣️ 2.3.2 rehash 方法


此放在也在上面的 addEntry 中使用过&#xff0c;用于扩容。就让我们深入的看看它的具体实现方式吧。

源码&#xff1a;

protected void rehash() {
// 获取旧的容量
int oldCapacity &#61; table.length;
Entry<?,?>[] oldMap &#61; table;
// overflow-conscious code
// 扩容&#xff0c;长度为两倍加1
int newCapacity &#61; (oldCapacity << 1) &#43; 1;
//判断新容量是否超出的最大值&#xff0c;如超出了更改为最大值
if (newCapacity - MAX_ARRAY_SIZE > 0) {
// 判断原来的容量是否就是最大值&#xff0c;如果是的就直接返回
if (oldCapacity &#61;&#61; MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity &#61; MAX_ARRAY_SIZE;
}
// 创建新的entry数组
Entry<?,?>[] newMap &#61; new Entry<?,?>[newCapacity];

// 操作数加1
modCount&#43;&#43;;
// 计算阀值
threshold &#61; (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE &#43; 1);
// 把新的entry赋值给table
table &#61; newMap;
// //循环遍历旧的元素并重新hash
for (int i &#61; oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old &#61; (Entry<K,V>)oldMap[i] ; old !&#61; null ; ) {
Entry<K,V> e &#61; old;
old &#61; old.next;
int index &#61; (e.hash & 0x7FFFFFFF) % newCapacity;
e.next &#61; (Entry<K,V>)newMap[index];
newMap[index] &#61; e;
}
}
}

具体流程已经在上方的注释中写过了&#xff0c;这里就不再具体赘述了。



♣️ 2.2.3 get 方法


源码&#xff1a;

// 加synchronized锁访问
public synchronized V get(Object key) {
Entry<?,?> tab[] &#61; table;
// 哈希 key 的值
int hash &#61; key.hashCode();
// 重新获取下标加索引
int index &#61; (hash & 0x7FFFFFFF) % tab.length;
// 循环遍历
for (Entry<?,?> e &#61; tab[index] ; e !&#61; null ; e &#61; e.next) {
// 判断 hash值equals是否相等
if ((e.hash &#61;&#61; hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}


♣️ 2.2.4 remove 方法


源码&#xff1a;

public synchronized V remove(Object key) { // 加锁&#xff0c;即线程安全
Entry<?,?> tab[] &#61; table;
// 获取hash值
int hash &#61; key.hashCode();
// 查找下标值
int index &#61; (hash & 0x7FFFFFFF) % tab.length;
&#64;SuppressWarnings("unchecked")
Entry<K,V> e &#61; (Entry<K,V>)tab[index];
// 循环遍历
for(Entry<K,V> prev &#61; null ; e !&#61; null ; prev &#61; e, e &#61; e.next) {
// 判断是否存在key
if ((e.hash &#61;&#61; hash) && e.key.equals(key)) {
// 操作数加一
modCount&#43;&#43;;
// 进行链表删除操作
if (prev !&#61; null) {
prev.next &#61; e.next;
} else {
tab[index] &#61; e.next;
}
// 总长度减1
count--;
V oldValue &#61; e.value;
e.value &#61; null;
return oldValue;
}
}
return null;
}

3、HashTable常见面试题

1、HashTable 中的默认值是多少?

2、HashTable 的底层数据结构是什么&#xff1f;

3、HashTable是否是线程安全的&#xff1f;

4、HashTable 每次扩容的容量是多大&#xff1f;

5、HashTable又和HashMap有什么区别呢&#xff1f;

参考答案地址&#xff1a; 地址







推荐阅读
  • 本文深入探讨了Java中的代理模式,包括静态代理和动态代理的概念、实现及其应用场景。通过具体的代码示例,详细解析了如何利用代理模式增强代码的功能性和灵活性。 ... [详细]
  • 本文探讨了在C++中如何有效地清空输入缓冲区,确保程序只处理最近的输入并丢弃多余的输入。我们将介绍一种不阻塞的方法,并提供一个具体的实现方案。 ... [详细]
  • 本文详细介绍了 org.apache.commons.io.IOCase 类中的 checkCompareTo() 方法,通过多个代码示例展示其在不同场景下的使用方法。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 任务,栈, ... [详细]
  • 本文详细介绍如何在Linux系统中配置SSH密钥对,以实现从一台主机到另一台主机的无密码登录。内容涵盖密钥对生成、公钥分发及权限设置等关键步骤。 ... [详细]
  • 本文详细介绍了如何在 Objective-C 中使用 @public 和 @protected 修饰符来控制类成员的访问权限。同时,探讨了点语法和箭头操作符的区别,以及属性声明和实现的自动生成。 ... [详细]
  • 本文探讨了使用C#在SQL Server和Access数据库中批量插入多条数据的性能差异。通过具体代码示例,详细分析了两种数据库的执行效率,并提供了优化建议。 ... [详细]
  • 反向投影技术主要用于在大型输入图像中定位特定的小型模板图像。通过直方图对比,它能够识别出最匹配的区域或点,从而确定模板图像在输入图像中的位置。 ... [详细]
  • 深入理解Lucene搜索机制
    本文旨在帮助读者全面掌握Lucene搜索的编写步骤、核心API及其应用。通过详细解析Lucene的基本查询和查询解析器的使用方法,结合架构图和代码示例,带领读者深入了解Lucene搜索的工作流程。 ... [详细]
  • 采用IKE方式建立IPsec安全隧道
    一、【组网和实验环境】按如上的接口ip先作配置,再作ipsec的相关配置,配置文本见文章最后本文实验采用的交换机是H3C模拟器,下载地址如 ... [详细]
  • 目录一、salt-job管理#job存放数据目录#缓存时间设置#Others二、returns模块配置job数据入库#配置returns返回值信息#mysql安全设置#创建模块相关 ... [详细]
  • 本文详细介绍了优化DB2数据库性能的多种方法,涵盖统计信息更新、缓冲池调整、日志缓冲区配置、应用程序堆大小设置、排序堆参数调整、代理程序管理、锁机制优化、活动应用程序限制、页清除程序配置、I/O服务器数量设定以及编入组提交数调整等方面。通过这些技术手段,可以显著提升数据库的运行效率和响应速度。 ... [详细]
  • 随着EOS主网的成功启动,众多开发者和投资者对其给予了高度关注。本文旨在介绍如何构建EOS开发环境,包括所需的基本硬件配置、软件安装步骤以及常见问题的解决方案。 ... [详细]
author-avatar
haha20101030
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有