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----------------------------------------------------------------------------");

目录
相关文章
|
13小时前
|
Java
Java中ReentrantLock中部分加锁取消节点源码分析
Java中ReentrantLock中部分加锁取消节点源码分析
30 13
|
13小时前
|
Java
Java中ReentrantLock中 lock.lock(),加锁源码分析
Java中ReentrantLock中 lock.lock(),加锁源码分析
16 0
|
13小时前
|
安全 Java 数据库连接
【Java每日一题】——第三十五题:一个父类Animal和两个子类Rabbit和Tiger描述动物世界的继承关系两个子类吃的行为各不相同(兔子吃草,老虎吃肉)但睡觉的行为是一致
【Java每日一题】——第三十五题:一个父类Animal和两个子类Rabbit和Tiger描述动物世界的继承关系两个子类吃的行为各不相同(兔子吃草,老虎吃肉)但睡觉的行为是一致
51 0
|
13小时前
|
安全 Java 数据库连接
【Java每日一题】——第三十四题:设计一个学生类Student和它的一个子类Undergraduate
【Java每日一题】——第三十四题:设计一个学生类Student和它的一个子类Undergraduate
106 0
|
13小时前
|
存储 安全 Java
Java容器类List、ArrayList、Vector及map、HashTable、HashMap
Java容器类List、ArrayList、Vector及map、HashTable、HashMap
|
13小时前
|
存储 缓存 安全
【 Java中String源码分析(JVM视角你不来看看?】
【 Java中String源码分析(JVM视角你不来看看?】
14 0
|
13小时前
|
存储 Java 索引
【亮剑】Java中的并发容器ConcurrentHashMap,它在JDK1.5中引入,用于替换HashTable和SynchronizedMap
【4月更文挑战第30天】本文介绍了Java中的并发容器ConcurrentHashMap,它在JDK1.5中引入,用于替换HashTable和SynchronizedMap。文章展示了创建、添加、获取、删除和遍历元素的基本用法。ConcurrentHashMap的内部实现基于分段锁,每个段是一个独立的Hash表,通过分段锁实现并发控制。每个段内部采用数组+链表/红黑树的数据结构,当冲突过多时转为红黑树优化查询。此外,它有扩容机制,当元素超过阈值时,会逐段扩容并翻倍Segment数量,以保持高性能的并发访问。
|
13小时前
|
存储 安全 Java
【JAVA】concurrentHashMap和HashTable有什么区别
【JAVA】concurrentHashMap和HashTable有什么区别
|
13小时前
|
Java
Java——子类的继承性
Java——子类的继承性
12 0
|
13小时前
|
SpringCloudAlibaba Java Maven
【问题篇】Caused by: java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/
【问题篇】Caused by: java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/
14 2