热门标签 | 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容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • Final关键字的含义及用法详解
    本文详细介绍了Java中final关键字的含义和用法。final关键字可以修饰非抽象类、非抽象类成员方法和变量。final类不能被继承,final类中的方法默认是final的。final方法不能被子类的方法覆盖,但可以被继承。final成员变量表示常量,只能被赋值一次,赋值后值不再改变。文章还讨论了final类和final方法的应用场景,以及使用final方法的两个原因:锁定方法防止修改和提高执行效率。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
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社区 版权所有