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

简介: 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函数第一行设置断点)

packagecsdn.knowledge.api_tools.gather.map;
importjava.util.Hashtable;
/*** @author : Cyan_RA9* @version : 21.0*/publicclassHashtable_Demo {
publicstaticvoidmain(String[] args) {
Hashtablehashtable=newHashtable();
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数组进行扩容。

     

       对于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类为演示类代码如下 :(代码中的注释要看)

packagecsdn.knowledge.api_tools.gather.map;
importjava.io.*;
importjava.util.Iterator;
importjava.util.Properties;
/*** @author : Cyan_RA9* @version : 21.0*/publicclassProperties_Demo {
publicstaticvoidmain(String[] args) throwsIOException {
//演示 : Properties类的特有功能//1.创建Properties类对象Propertiesproperties=newProperties();
//2.利用字节输入流读取demo1.properties文件中的内容InputStreaminputStream=newBufferedInputStream(newFileInputStream("D:\\JAVA\\IDEA\\file\\demo1.properties"));
//3.利用Properties类的load方法,将文件中的属性列表加载到Properties类对象中。properties.load(inputStream);
//4.利用迭代器遍历key的集合。Iterator<String>iterator=properties.stringPropertyNames().iterator();
while (iterator.hasNext()) {
Stringkey=iterator.next();
//5.根据获得的key来获取对应的value。System.out.println(key+":"+properties.getProperty(key));
        }
//6.利用字节输出流保存属性到demo2.properties文件(PS : true表示追加开启)FileOutputStreamfileOutputStream=newFileOutputStream("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----------------------------------------------------------------------------");

目录
相关文章
|
3月前
|
Rust Java 文件存储
Java系统中的错误码设计问题之通过properties文件管理Error Code如何解决
Java系统中的错误码设计问题之通过properties文件管理Error Code如何解决
51 1
|
1月前
|
Java
Java基础之 JDK8 HashMap 源码分析(中间写出与JDK7的区别)
这篇文章详细分析了Java中HashMap的源码,包括JDK8与JDK7的区别、构造函数、put和get方法的实现,以及位运算法的应用,并讨论了JDK8中的优化,如链表转红黑树的阈值和扩容机制。
23 1
|
1月前
|
Java 编译器
【一步一步了解Java系列】:子类继承以及代码块的初始化
【一步一步了解Java系列】:子类继承以及代码块的初始化
24 3
|
1月前
|
Java
java中父类方法return this.对象还是变量,子类去调用this.这个方法的问题
本文探讨了在Java中,当父类的方法返回`this`对象或变量时,子类调用该方法的行为,以及`this`关键字在不同类中调用方法时的指向问题。
15 0
java中父类方法return this.对象还是变量,子类去调用this.这个方法的问题
|
1月前
|
Java 程序员 编译器
【Java】继承、super、final、子类构造方法
【Java】继承、super、final、子类构造方法
28 0
|
3月前
|
Java 程序员
Java系统中的错误码设计问题之实现一个基于properties文件的错误消息管理系统如何解决
Java系统中的错误码设计问题之实现一个基于properties文件的错误消息管理系统如何解决
23 1
|
3月前
|
安全 Java
【Java集合类面试十五】、说一说HashMap和HashTable的区别
HashMap和Hashtable的主要区别在于Hashtable是线程安全的,不允许null键和值,而HashMap是非线程安全的,允许null键和值。
|
3月前
|
网络协议 Java 应用服务中间件
Tomcat源码分析 (一)----- 手撕Java Web服务器需要准备哪些工作
本文探讨了后端开发中Web服务器的重要性,特别是Tomcat框架的地位与作用。通过解析Tomcat的内部机制,文章引导读者理解其复杂性,并提出了一种实践方式——手工构建简易Web服务器,以此加深对Web服务器运作原理的认识。文章还详细介绍了HTTP协议的工作流程,包括请求与响应的具体格式,并通过Socket编程在Java中的应用实例,展示了客户端与服务器间的数据交换过程。最后,通过一个简单的Java Web服务器实现案例,说明了如何处理HTTP请求及响应,强调虽然构建基本的Web服务器相对直接,但诸如Tomcat这样的成熟框架提供了更为丰富和必要的功能。
|
3月前
|
存储 缓存 安全
深度剖析Java HashMap:源码分析、线程安全与最佳实践
深度剖析Java HashMap:源码分析、线程安全与最佳实践
|
11天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。