热门标签 | 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; 地址







推荐阅读
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • Scala 实现 UTF-8 编码属性文件读取与克隆
    本文介绍如何使用 Scala 以 UTF-8 编码方式读取属性文件,并实现属性文件的克隆功能。通过这种方式,可以确保配置文件在多线程环境下的一致性和高效性。 ... [详细]
  • 本文详细介绍了Java中org.neo4j.helpers.collection.Iterators.single()方法的功能、使用场景及代码示例,帮助开发者更好地理解和应用该方法。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • 本文深入探讨了 Java 中的 Serializable 接口,解释了其实现机制、用途及注意事项,帮助开发者更好地理解和使用序列化功能。 ... [详细]
  • DNN Community 和 Professional 版本的主要差异
    本文详细解析了 DotNetNuke (DNN) 的两种主要版本:Community 和 Professional。通过对比两者的功能和附加组件,帮助用户选择最适合其需求的版本。 ... [详细]
  • 本文详细介绍了如何构建一个高效的UI管理系统,集中处理UI页面的打开、关闭、层级管理和页面跳转等问题。通过UIManager统一管理外部切换逻辑,实现功能逻辑分散化和代码复用,支持多人协作开发。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • IneedtofocusTextCellsonebyoneviaabuttonclick.ItriedlistView.ScrollTo.我需要通过点击按钮逐个关注Tex ... [详细]
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社区 版权所有