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

news2024/11/24 5:09:29

【学习笔记】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 = new Hashtable<>();

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

源码:

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

继承图:

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

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

Dictionary:

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

    /**
     * 一个用于计算长度的抽象方法
     */
    abstract public int size();

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

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

    /**
     * 一个用于取出value的抽象方法,取出类型为枚举
     */
    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 。因为其是 抽象类 除了构造函数都是抽象方法,当 HashTable 继承 Dictionary 的时候则需要实现其的所有 抽象方法

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

2.1 HashTalbe 中常用的变量

源码:

    /**
     * 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).)
     *
     * @serial
     */
    private int threshold;

    /**
     * The load factor for the hashtable.
     *
     * @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 = 0;

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = 1421746759512286392L;

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

2.2 HashTable 中的构造函数

源码:

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

    /**
     * Constructs a new, empty hashtable with the specified initial capacity
     * and default load factor (0.75).
     *
     * @param     initialCapacity   the initial capacity of the hashtable.
     * @exception IllegalArgumentException if the initial capacity is less
     *              than zero.
     */
		// 定义初始值,因子为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,负载因子为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).
     *
     * @param t the map whose mappings are to be placed in this map.
     * @throws NullPointerException if the specified map is null.
     * @since   1.2
     */
    public Hashtable(Map<? extends K, ? extends V> t) {
      	// 比较两倍的Map长度和11谁大,不免不必要的空间浪费
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

通过源码我们能知道 HashTable 有四种构造函数:

  • Hashtable()
  • Hashtable(Map<? extends K, ? extends V> t)
  • Hashtable(int initialCapacity)
  • public Hashtable(int initialCapacity, float loadFactor)

2.3 HashTable 中常用的方法

♣️ 2.3.1 方法

源码:

    	// 加锁
			public synchronized V put(K key, V value) {
        // Make sure the value is not null
        // 如果value为null,则抛出 NullPointerException 异常
        if (value == null) {
            throw new NullPointerException();
        }

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

通过上述代码我们能知道 HashTable 有一下几个特点:

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

补充 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
        Entry<K,V> e = (Entry<K,V>) tab[index];
      	// 放入数组中
        tab[index] = new Entry<>(hash, key, value, e);
      	// hashtable长度加1
        count++;
    }

♣️ 2.3.2 rehash 方法

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

源码:

    protected void rehash() {
      	// 获取旧的容量
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
      	// 扩容,长度为两倍加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;
        }
      	// 创建新的entry数组
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
				
      	// 操作数加1
        modCount++;
      	// 计算阀值
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
      	// 把新的entry赋值给table
        table = newMap;

      	// //循环遍历旧的元素并重新hash
        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;
            }
        }
    }

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

♣️ 2.2.3 get 方法

源码:

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

♣️ 2.2.4 remove 方法

源码:

    public synchronized V remove(Object key) { // 加锁,即线程安全
        Entry<?,?> tab[] = table;
      	// 获取hash值
        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) {
          	// 判断是否存在key
            if ((e.hash == hash) && e.key.equals(key)) {
              	// 操作数加一
                modCount++;
              	// 进行链表删除操作
                if (prev != null) {
                    prev.next = e.next;
                } else {
                    tab[index] = e.next;
                }
              	// 总长度减1
                count--;
                V oldValue = e.value;
                e.value = null;
                return oldValue;
            }
        }
        return null;
    }

3、HashTable常见面试题

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

2、HashTable 的底层数据结构是什么?

3、HashTable是否是线程安全的?

4、HashTable 每次扩容的容量是多大?

5、HashTable又和HashMap有什么区别呢?

参考答案地址: 地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/108572.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenCV基础入门

主要了解包括 opencv 的下载和环境配置opencv目录的了解opencv中highgui模块opencv中core模块opencv中imgproc模块opencv中feature2d模块opencv视频操作 1.OpenCV简介 图像是人类视觉的基础&#xff0c;是自然景物的客观反映。 模拟图像通过某种物理量的强弱变化来记录图像…

中小企业远程办公指南:10分钟搭建,即插即用

不装了 我成小阳人了 虽然还没算过来人&#xff0c;但是想要提醒一下小伙伴“能不阳就别阳”&#xff0c;“能晚阳就晚阳”&#xff01; 真的很痛很难受。 为了应对即将到来的高峰&#xff0c;我们在上周末紧急采取了远程居家办公模式。 不得不说&#xff0c;公司应对突发情…

彻底卸载2345王牌输入法的方法

2345王牌输入法是2345公司旗下一款中文输入法软件&#xff0c;主打纯净输入&#xff0c;有用户用了一段时间觉得不太习惯&#xff0c;就想卸载装别的软件&#xff0c;但是发现怎么也卸不掉&#xff0c;下面小编就给大家介绍彻底卸载2345王牌输入法的方法。 方法一&#xff1a;使…

线性代数 --- Gauss消元的部分主元法和完全主元法(补充)

Gauss消元的部分主元法和完全主元法(补充) 本文主要是对下文的补充&#xff0c;而补充的主要内容就是如何直接求出(手动)部分主元法的P矩阵和L矩阵&#xff1a; 线性代数 --- Gauss消元的部分主元法和完全主元法_松下J27的博客-CSDN博客_高斯消元的主元是什么Gauss消元的部分主…

MyBatisPlus ---- MyBatis-Plus简介

MyBatisPlus ---- MyBatis-Plus简介1. 简介2. 特性3. 支持数据库4. 框架结构1. 简介 MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 愿景&#xff1a; 我们的…

艾美捷内皮血管生成检测试剂盒的多种特点

血管生成&#xff08;Angiogenesis&#xff09;是指源于已存在的毛细血管和毛细血管后微静脉的新的毛细血管性血管的生长。内皮血管生成是一个极其复杂的过程。通常新生血管是在原有的血管基础上延伸扩展而形成的&#xff0c;其过程类似于典型的伤口愈合和胚胎形成过程。在血管…

RK3568下载SDK编译源码

前面我们已经搭建好了编译一个环境https://blog.csdn.net/qq_24093081/article/details/128394606 所以在这里我们就需要下载瑞芯微提供的SDK进行编译&#xff0c;由于RK3568支持多种系统&#xff0c;比如Android11&#xff0c;Linux&#xff08;debian、Ubuntu、buildroot、yo…

TCP网络编程

1. 网络相关概念 网络通信&#xff1a;两台设备之间通过网络实现数据传输&#xff1b;java.net包下提供了一系列的类或接口&#xff0c;完成网络通信&#xff1b; 局域网&#xff1a;覆盖一个学校、单位、公司&#xff1b; 城域网&#xff1a;覆盖一个城市&#xff1b; 广域网…

力扣(15.18)补9.19

15.三数之和 我以为不会太难&#xff0c;md不会。 其实很让我惊讶的是&#xff0c;双指针用了2层循环但复杂度确是O&#xff08;n&#xff09;。牛&#x1f42e;&#x1f42e;&#x1f42e;&#x1f42e;&#x1f42e;&#x1f42e;&#x1f42e;&#x1f42e;&#x1f42e;&am…

自动控制原理笔记-结构图及其等效变换

目录 结构图&#xff1a; 得到系统结构图的两种方式 &#xff1a; 方框图画结构图&#xff1a; 微分方程组画结构图&#xff1a; 结构图等效变换规则&#xff1a; 结构图的化简&#xff1a; 小结&#xff1a; 结构图&#xff1a; 得到系统结构图的两种方式 &#xff1a…

WebMagic

1.介绍 WebMagic是一款简单灵活的爬虫框架。基于它你可以很容易的编写一个爬虫。 WebMagic由四个组件(Downloader、PageProcessor、Scheduler、Pipeline)构成&#xff0c;核心代码非常简单&#xff0c;主要是将这些组件结合并完成多线程的任务。这意味着&#xff0c;在WebMag…

K8S——存储ConfigMap

configMap描述信息 ConfigMap 功能在 Kubernetes1.2 版本中引入&#xff0c;许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。 ConfigMap API 给我们提供了向容器中注入配置信息的机制&#xff0c;ConfigMap 可以被用来保存单个属性&#xff0c;也可以用来保…

hutool工具并发使用 Sftp sftp = new Sftp(sshHost, sshPort, sshUser, sshPass) 的坑

目录问题描述解决方案Sftp(sshHost, sshPort, sshUser, sshPass) 分析Sftp(Session session) 分析吐槽另外还有一坑 delFile(String filePath)问题描述 看到前人的代码中使用 Sftp sftp new Sftp(sshHost, sshPort, sshUser, sshPass) ; 来创建一个Sftp客户端&#xff0c;然后…

Automatic Exposure Correction of Consumer Photographs 分析

文章目录Automatic Exposure Correction of Consumer Photographs1. 图像分割2. 按灰度区域合并3. 根据细节多少和各zone相对对比度约束&#xff0c;求解每个zone对应的 最优zone.4. 每个zone以及对应的最有zone找到之后&#xff0c;可以求解多项式curve的 ϕs\phi_sϕs​ 和 ϕ…

【Flask框架】——24 创建ROM映射

创建ROM映射 ORM&#xff1a;Object Relationship Mapping 创建一个类&#xff0c;一个类对应了一个数据库中的一张表&#xff0c;类的数据属性对应了表中的字段名&#xff0c;这个类称为映射类。 根据映射类创建出一个一个的对象&#xff0c;每个对象对应了表中的一条实际的…

函数(6)

目录 1、函数是什么&#xff1f; 2、C语言中函数的分类&#xff1a; 1、库函数 2、自定义函数 3、函数的参数 4、函数的调用 5、练习 1、打印100~200之间的素数 2、打印100~200之间的闰年 3、写一个函数&#xff0c;实现一个整形有序数组的二分查找 6、函数的嵌套调…

CSDN每日一练求最小元素 C语言

题目名称&#xff1a;求最小元素 时间限制&#xff1a;1000ms内存限制&#xff1a;256M 题目描述 Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]). Find the minimum…

MS17-010漏洞攻击与防御(利用永恒之蓝攻击Win7系统)

任务1 利用永恒之蓝攻击Win7系统 在Kali终端中输入命令“msfconsole ”&#xff0c;启动Metasploit&#xff1b;输入命令“use auxiliary/scanner/smb/smb_ms17_010”&#xff0c;加载扫描模块&#xff1b;输入命令“set RHOSTS 192.168.0.6”&#xff0c;设置需要被扫描的目标…

哈希表题目:环形链表 II

文章目录题目标题和出处难度题目描述要求示例数据范围进阶解法一思路和算法代码复杂度分析解法二思路和算法证明代码复杂度分析题目 标题和出处 标题&#xff1a;环形链表 II 出处&#xff1a;142. 环形链表 II 难度 2 级 题目描述 要求 给你一个链表的头结点 head\tex…

CSDN周赛第16期-100分满分题解

前言 这是时隔两年再参加比赛了&#xff0c;上次参加算法竞赛还是2020年在公司1024活动的时候。当时获得了二等奖&#xff08;switch套装&#xff09;和一个快题奖&#xff08;小米行李箱&#xff09;。 这次比赛获得了满分&#xff0c;也还不错。题目除了二维积水的问题&…