java Hashtable及其子类Properties 源码分析(通俗易懂)

举报
Cyan_RA9 发表于 2023/04/11 19:56:10 2023/04/11
【摘要】 java 集合篇章——Hashtable 以及子类 Properties 内容分享。

目录

一、前言

二、Hashtable详解

        1.简介

        2.特点

        3.底层实现

        4.HashMap VS Hashtable

三、Properties详解

        1.简介

        2.特点

        3.具体使用(可以不看)

四、完结撒❀


一、前言

        大家好,本篇博文是对Map接口常用实现类之一Hashtable类的源码分析,顺便讲一下它的子类Properties,考虑到Hashtable的使用频率,up不会像HashMap那样讲得很细致,但是底层的东西该说都会说的,比一般地方讲得还是要细点。

        注意 : ①解读源码需要扎实的基础,比较适合希望深究的同学;不要眼高手低,看会了不代表你会了,自己能跟着过一遍才算有收获; 点击文章的侧边栏目录或者前面的目录可以进行跳转。 良工不示人以朴,up所有文章都会适时改进。大家有问题都可以在评论区讨论交流,或者私信up。 感谢阅读!


二、Hashtable详解

        1.简介

        Hashtable是Map接口的一个实现类,地位上与HashMap平起平坐。Hashtable也属于java.base模块,java.util包下,如下图所示 : 

image.png

        我们再来看看Hashtable的类图,如下

image.png


        2.特点

        Hashtable中保存的也是key-value键值对,不过特别的是Hashtable键值对中的"key"和"value"都不能为null,否则会抛出nullPointerException异常

        2° Hashtable中的一些常用方法在用法上基本和HashMap中的一致,比如put方法。

        Hashtable中的成员方法用了synchronized关键字修饰,因此Hashtable是线程安全的,而HashMap是线程不安全的。


        3.底层实现

       

        为了通过Debug来给大家分析Hashtable的源码,up以Hashtable_Demo类作为演示类来进行断点调试。Hashtable_Demo类代码如下 : (main函数第一行设置断点)

package csdn.knowledge.api_tools.gather.map;
import java.util.Hashtable;
/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class Hashtable_Demo {
    public static void main(String[] args) {
        Hashtable hashtable = new Hashtable();
        hashtable.put(1, 141);
        hashtable.put(2, 141);
        hashtable.put(3, 141);
        hashtable.put(4, 141);
        hashtable.put(5, 141);
        hashtable.put(6, 141);
        hashtable.put(7, 141);
        hashtable.put(8, 141);
        hashtable.put(9, "这是集合第9个元素捏");
        System.out.println(hashtable);
    }
}

image.gif

        1°
       
Hashtable底层维护了Hashtable$Entry类型的数组table,用于存放键值对,如下图所示 : 

image.png

        通过查看Hashtable的源码能够找到这个Entry类型,如下图所示 : 

image.png

        可以看到,Hashtable$Entry其实就是Hashtable的一个静态内部类,并且同HashMap中的内部类HashMap$Node类一样,也实现了Map接口中的Entry内部接口

       

        利用无参构造初始化Hashtable类对象时,会将底层的table数组初始化为长度等于11的数组。同样我们可以通过追溯Hashtable无参构造的源码来找到依据,如下 : 

image.png

        可以看到,Hashtable的无参构造,其实底层调用的是本类的一个有参构造,并且通过传入的实参我们可以得到以下信息——table数组的初始容量 = 11;加载因子 = 0.75。这俩就不用我多说了,都是老面孔了。

        我们可以进入追进入这个有参构造中看看,如下图所示 : 

image.png

        别的不说,直接看最后两行将new出的长度 = 11的Entry类型的数组赋值给了table数组,这条语句解释了table数组的初始容量为什么是11。

        同时,threshold(临界值)被赋值为8。这个8是怎么来的?其实和我们之前讲得HashMap底层一样,threshold = table数组的长度 * 加载因子 = 11 * 0.75 = 8.25,因为此处进行了int类型的强制向下转型,所以最后赋值给threshold变量的是8,初始临界值 = 8。

        关于"临界值有什么用"的问题up在之前的源码分析中讲过很多次了,这里只回顾一点——在HashMap底层中,当table数组中元素的个数超过临界值,就要对table数组进行扩容。

        3° 

        对于Hashtable底层的table数组,其扩容时采用了"2n + 1"的机制。具体解释我们可以结合源码来看,如下图所示 : 

image.png

        先向table数组中加入8个元素,可以看到此时集合中键值对的个数count = 8,等于此时table数组的临界值threshold。此时,如果我们向集合中添加第9个元素,table数组就会进行扩容操作。我们可以通过Debug,来追一下添加第9个元素的put方法。首先我们进入put方法,如下图所示 : 

image.png

image.png

        在put方法中我们发现,真正完成添加元素的操作是在addEntry方法中进行的,因此,我们继续追入addEntry方法如下图所示

image.png

        观察addEntry方法的源码,我们可以清楚地看到Hashtable类和HashMap类在扩容时明显的两点不同——

        ①具体代码的执行顺序不同 :
       
HashMap底层是先完成添加元素的操作,再进行临界值的判断——如果当前元素加入集合后,会使得集合中元素的个数大于临界值threshold,就会跳入resize方法对table数组进行扩容

        而Hashtable则是先进行临界值的判断,再完成添加元素的操作——如果当前集合中元素的个数已经达到或超过当前的临界值,就会先跳入rehash方法对table数组进行扩容操作,等扩容成功后,再回来把当前键值对加入table数组中

        ②对临界值的判断条件不同 : 

        HashMap底层对于临界值的判断条件是——如果当前集合中元素的个数大于当前临界值threshold,就进入resize方法对table数组进行扩容

        PS : 大家可能对HashMap底层的一些东西忘了,up将图放下面 : 

image.png

        而Hashtable对于临界值的判断条件却是——如果当前集合中元素的个数大于等于(达到或超过)当前临界值threshold,就进入rehash方法对table数组进行扩容

        当然,以上两点只是内在底层的一些不同,如果从外在宏观上来看,从结果上来看,其实这俩还是一样的😂,都是在添加超过临界值的那个元素后,table数组完成了扩容。

       

        说了这么多,咱们还不知道Hashtable到底是怎么扩容的呢。

        前面我们说到,对Hashtable底层的table数组执行扩容操作的是rehash方法,那么其扩容机制到底是个甚呢?接下里,我们就追入rehash方法瞧瞧,如下所示 :

image.png

         可以看到,有一条非常关键的语句"int newCapacity = (oldCapacity << 1) + 1;",它可以明确地告诉我们——扩容后新数组的长度newCapacity,就等于旧数组的长度左移一位再加1,相当于旧数组的长度 * 2 再 + 1。因此我们上文才说——Hashtable底层的table数组在扩容时采取了"2n + 1"的扩容机制。所以,扩容后table数组的长度就应该 = 11 * 2 + 1 = 23,对应的临界值 = (int) 23 * 0.75 = 17。如下图所示 : 

image.png

        4.HashMap VS Hashtable

        HashMap和Hashtable的具体对比图如下 : 

image.png


三、Properties详解

        1.简介

        Properties是Hashtable的子类,因此Properties也间接实现了Map接口Properties类也是采用key-value键值对的形式来存放数据

        2.特点

        作为Hashtable的子类,Properties的特点与Hashtable类似,比如不允许null,以及线程同步等等。

        Properties可以从___.properties文件对应的文件输入流中加载数据到Properties类对象,并进行数据的读取和修改。(PS : ___.properties文件通常指的是配置文件,例如数据库中保存用户信息的配置文件)。

        3.具体使用(可以不看

        关于Properties类对象进行元素添加删除等基本操作,up这里就不演示了。我们直接来看看Properties特别的地方,即——对___.properties文件中的属性列表进行读取和修改的操作。

        鉴于此部分内容涉及到一些异常类,File类,IO流等基础知识,如果你还没有学完这些内容,建议你先去把这些核心基础搞定(可以去参考up之前写得关于这些内容的万字详解系列博文),搞定之后再回来看这个。当然,如果你已经是学过这些知识了,看博文就是图个查缺补漏,那接下来的代码对你来说就不成问题。

        我们先创建一个properties文件,并保存好该文件的路径,如下图所示 :

image.png

        该properties文件内容如下 : 

image.png

        我们的目的就是实现——在控制台打印出demp1.properties文件中的属性类别,并将demo1.properties文件中的属性列表读取到demo2.properties文件中,并且再向新的demo2.properties文件中追加我们希望的新的数据

        up以Properties_Demo类为演示类代码如下 : (代码中的注释要看)

package csdn.knowledge.api_tools.gather.map;
import java.io.*;
import java.util.Iterator;
import java.util.Properties;
/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class Properties_Demo {
    public static void main(String[] args) throws IOException {
    //演示 : Properties类的特有功能
        //1.创建Properties类对象
        Properties properties = new Properties();
        //2.利用字节输入流读取demo1.properties文件中的内容
        InputStream inputStream =
                new BufferedInputStream(new FileInputStream("D:\\JAVA\\IDEA\\file\\demo1.properties"));
        //3.利用Properties类的load方法,将文件中的属性列表加载到Properties类对象中。
        properties.load(inputStream);
        //4.利用迭代器遍历key的集合。
        Iterator<String> iterator = properties.stringPropertyNames().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
        //5.根据获得的key来获取对应的value。
            System.out.println(key + ":" + properties.getProperty(key));
        }
        //6.利用字节输出流保存属性到demo2.properties文件(PS : true表示追加开启)
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\JAVA\\IDEA\\file\\demo2.properties", true);
        properties.setProperty("platform", "CSDN");
        properties.store(fileOutputStream, "demo2 for properties,just a demonstration.");
        /*
            store方法可以将当前Properties类对象中保存的属性列表存储到目的地文件中。
            传入的字符串可以作为注释出现在新的文件的头部。
         */
        //7.释放资源
        inputStream.close();
        fileOutputStream.close();
    }
}

image.gif

        运行结果 : 

image.png

        在目的地文件的路径下,我们可以看到新的properties文件已经成功生成,如下图所示 : 

image.png

        打开demo2.properties文件,如下图所示 : 

image.png

        可以看到,不仅原本的数据都保存了下来,新的属性列表也成功追加了进来。 


四、完结撒❀

        🆗,以上就是我们Hashtable及其子类Properties讲解的全部内容了。内容本身还是没什么难度的,主要就是给大家过一下,让大家知道——噢,Map接口的这个实现类是这么个回事。更多精彩内容,关注up不迷路。感谢阅读!

        System.out.println("END----------------------------------------------------------------------------"); 

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。