深入浅出的分析 Properties

作者:炸鸡可乐
原文出处:www.pzblog.cn

一、摘要

在集合系列的第一章,咱们了解到,Map 的实现类有 HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties 等等。

深入浅出的分析 Properties

在上一章节中,咱们介绍到 Hashtable 的数据结构和算法实现,在 Java 中其实还有一个非常重要的类 Properties,它继承自 Hashtable,主要用于读取配置文件。

本文通过看 JDK 和一些网友的博客总结,主要从 Properties 的用法实例来做介绍,如果有理解不当之处,欢迎指正。

二、简介

Properties 类是 java 工具包中非常重要的一个类,比如在实际开发中,有些变量,我们可以直接硬写入到自定义的 java 枚举类中。

但是有些变量, 在测试环境、预生产环境、生产环境,变量所需要取的值都不一样,这个时候,我们可以通过使用 properties 文件来加载程序需要的配置信息,以达到一行代码,多处环境都可以运行的效果!

最常见的比如 JDBC 数据源配置文件, properties文件以 .properties作为后缀,文件内容以 键=值格式书写,左边是变量名称,右边是变量值,用 #做注释,比如新建一个 jdbc.properties文件,内容如下:

深入浅出的分析 Properties

Properties 类是 properties 文件和程序的中间桥梁,不论是从 properties 文件读取信息,还是写入信息到 properties 文件,都要经由 Properties 类。

好了,唠叨了这么多,咱们回到本文要介绍的主角 Properties

从集合 Map 架构图可以看出,Properties 继承自 Hashtable,表示一个持久的 map 集合,属性列表以 key-value 的形式存在,Properties 类定义如下:

public class Properties extends Hashtable {
    ......

}

Properties 除了继承 Hashtable 中所定义的方法,Properties 也定义了以下几个常用方法,如图所示:

深入浅出的分析 Properties

2.1、常用方法介绍

2.1.1、set 方法(添加修改元素)

set 方法是将指定的 key, value 对添加到 map 里,在添加元素的时候,调用了 Hashtable 的 put 方法, 与 Hashtable 不同的是, key 和 value 都是字符串。

打开 Properties 的 setProperty 方法,源码如下:

public synchronized Object setProperty(String key, String value) {
        //调用父类 Hashtable 的 put 方法
        return put(key, value);
}

方法测试如下:

public static void main(String[] args) {
        Properties properties = new Properties();
        properties.setProperty("name1","张三");
        properties.setProperty("name2","张四");
        properties.setProperty("name3","张五");
        System.out.println(properties.toString());
}

输出结果:

{name3=张五, name2=张四, name1=张三}
2.1.2、get 方法(搜索指定元素)

get 方法根据指定的 key 值返回对应的 value,第一步是从调用 Hashtable 的 get 方法,如果有返回值,直接返回;如果没有返回值,但是初始化时传入了 defaults变量,从 defaults 变量中,也就是 Properties 中,去搜索是否有对于的变量,如果有就返回元素值。

打开 Properties 的 getProperty 方法,源码如下:

public String getProperty(String key) {
        //调用父类 Hashtable 的 get 方法
        Object oval = super.get(key);
        String sval = (oval instanceof String) ? (String)oval : null;
         //进行变量非空判断
        return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
}

查看 defaults 这个变量,源码如下:

public class Properties extends Hashtable {
    protected Properties defaults;
}

这个变量在什么时候赋值呢,打开源码如下:

public Properties(Properties defaults) {
        this.defaults = defaults;
}

可以发现,在 Properties 构造方法初始化阶段,如果你给了一个自定义的 defaults ,当调用 Hashtable 的 get 方法没有搜索到元素值的时候,并且 defaults 也不等于空,那么就会进一步在 defaults 里面进行搜索元素值。

方法测试如下:

public static void main(String[] args) {
        Properties properties = new Properties();
        properties.setProperty("name1","张三");
        properties.setProperty("name2","张四");
        properties.setProperty("name3","张五");
        //将 properties 作为参数初始化到 newProperties 中
        Properties newProperties = new Properties(properties);
        newProperties.setProperty("name4","李三");
        //查询key中 name1 的值
        System.out.println("查询结果:" + properties.getProperty("name1"));
}

输出结果:

通过key查询结果:张三
2.1.3、load方法(加载配置文件)

load 方法,表示将 properties 文件以输入流的形式加载文件,并且提取里面的键、值对,将键值对元素添加到 map 中去。

打开 Properties 的 load 方法,源码如下:

public synchronized void load(InputStream inStream) throws IOException {
        //读取文件流
        load0(new LineReader(inStream));
}

load0 方法,源码如下:

private void load0 (LineReader lr) throws IOException {
    char[] convtBuf = new char[1024];
    int limit;
    int keyLen;
    int valueStart;
    char c;
    boolean hasSep;
    boolean precedingBackslash;

    //一行一行的读取
    while ((limit = lr.readLine()) >= 0) {
        c = 0;
        keyLen = 0;
        valueStart = limit;
        hasSep = false;

        precedingBackslash = false;
        //判断key的长度
        while (keyLen < limit) {
            c = lr.lineBuf[keyLen];
            if ((c == '=' ||  c == ':') && !precedingBackslash) {
                valueStart = keyLen + 1;
                hasSep = true;
                break;
            } else if ((c == ' ' || c == '\t' ||  c == '\f') && !precedingBackslash) {
                valueStart = keyLen + 1;
                break;
            }
            if (c == '\\') {
                precedingBackslash = !precedingBackslash;
            } else {
                precedingBackslash = false;
            }
            keyLen++;
        }
        //获取值的起始位置
        while (valueStart < limit) {
            c = lr.lineBuf[valueStart];
            if (c != ' ' && c != '\t' &&  c != '\f') {
                if (!hasSep && (c == '=' ||  c == ':')) {
                    hasSep = true;
                } else {
                    break;
                }
            }
            valueStart++;
        }
        //获取文件中的键和值参数
        String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
        String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
        //调用 Hashtable 的 put 方法,将键值加入 map 中
        put(key, value);
    }
}

好了,我们来在 src/recources目录下,新建一个 custom.properties配置文件,内容如下:

#定义一个变量名称和值
userName=李三
userPwd=123456
userAge=18
userGender=男
userEmail=123@123.com

方法测试如下:

public class TestProperties  {

    public static void main(String[] args) throws Exception {
        //初始化 Properties
        Properties prop = new Properties();
        //加载配置文件
        InputStream in = TestProperties .class.getClassLoader().getResourceAsStream("custom.properties");
        //读取配置文件,指定编码格式,避免读取中文乱码
        prop.load(new InputStreamReader(in, "UTF-8"));
        //将内容输出到控制台
        prop.list(System.out);
    }
}

输出结果:

userPwd=123456
userEmail=123@123.com
userAge=18
userName=李三
userGender=男
2.1.4、propertyNames方法(读取全部信息)

propertyNames 方法,表示读取 Properties 的全部信息, 本质是创建一个新的 Hashtable 对象,然后将原 Hashtable 中的数据复制到新的 Hashtable 中,并将 map 中的 key 全部返回。

打开 Properties 的 propertyNames 方法,源码如下:

public Enumeration propertyNames() {
        Hashtable h = new Hashtable<>();
        //将原 map 添加到新的 Hashtable 中
        enumerate(h);
        //返回 Hashtable 中全部的 key 元素
        return h.keys();
}

enumerate 方法,源码如下:

private synchronized void enumerate(Hashtable h) {
        //判断 Properties 中是否有初始化的配置文件
        if (defaults != null) {
            defaults.enumerate(h);
        }
        //将原 Hashtable 中的数据添加到新的 Hashtable 中
        for (Enumeration e = keys() ; e.hasMoreElements() ;) {
            String key = (String)e.nextElement();
            h.put(key, get(key));
        }
}

方法测试如下:

public static void main(String[] args) throws Exception {
        //初始化 Properties
        Properties prop = new Properties();
        //加载配置文件
        InputStream in = TestProperties.class.getClassLoader().getResourceAsStream("custom.properties");
        //读取配置文件,指定读取编码 UTF-8,防止内容乱码
        prop.load(new InputStreamReader(in, "UTF-8"));
        //获取 Properties 中全部的 key 元素
        Enumeration enProp = prop.propertyNames();
        while (enProp.hasMoreElements()){
            String key = (String) enProp.nextElement();
            String value = prop.getProperty(key);
            System.out.println(key + "=" + value);
        }
}

输出内容如下:

userPwd=123456
userEmail=123@123.com
userAge=18
userName=李三
userGender=男
2.1.5、总结

Properties 继承自 Hashtable,大部分方法都复用于 Hashtable,比如,get、put、remove、clear 方法, 与 Hashtable 不同的是, Properties中的 key 和 value 都是字符串,如果需要获取 properties 中全部内容,可以先通过迭代器或者 propertyNames 方法获取 map 中所有的 key 元素,然后遍历获取 key 和 value。

需要注意的是,Properties 中的 setProperty 、load 方法,都加了 synchronized同步锁,用来控制线程同步。

三、properties 文件的加载方式

在实际开发中, 经常会遇到读取配置文件路径找不到,或者读取文件内容乱码的问题,下面简单介绍一下,properties 文件的几种常用的加载方式。

properties 加载文件的方式,大致可以分两类,第一类是使用 java.util.Properties 的 load 方法来加载文件流;第二类是使用 java.util.ResourceBundle 类来获取文件内容。

src/recources目录下,新建一个 custom.properties配置文件,文件编码格式为 UTF-8,内容还是以刚刚那个测试为例,各个加载方式如下!

3.1、通过文件路径来加载文件

这类方法加载文件,主要是调用 Properties 的 load 方法,获取文件路径,读取文件以流的形式加载文件。

方法如下:

Properties prop = new Properties();
//获取文件绝对路径
String filePath = "/coding/java/src/resources/custom.properties";
//加载配置文件
InputStream in = new FileInputStream(new File(filePath));
//读取配置文件
prop.load(new InputStreamReader(in, "UTF-8"));
System.out.println("userName:"+prop.getProperty("userName"));

输出结果:

userName&#xFF1A;&#x674E;&#x4E09;

3.2、通过当前类加载器的getResourceAsStream方法获取

这类方法加载文件,也是调用 Properties 的 load 方法,不同的是,通过类加载器来获取文件路径,如果当前文件是在 src/resources目录下,那么直接传入文件名就可以了。

方法如下:

Properties prop = new Properties();
//加载配置文件
InputStream in = TestProperties.class.getClassLoader().getResourceAsStream("custom.properties");
//读取配置文件
prop.load(new InputStreamReader(in, "UTF-8"));
System.out.println("userName:"+prop.getProperty("userName"));

输出结果:

userName&#xFF1A;&#x674E;&#x4E09;

3.3、使用ClassLoader类的getSystemResourceAsStream方法获取

和上面类似,也是通过类加载器来获取文件流,方法如下:

Properties prop = new Properties();
//加载配置文件
InputStream in = ClassLoader.getSystemResourceAsStream("custom.properties");
//读取配置文件
prop.load(new InputStreamReader(in, "UTF-8"));
System.out.println("userName:"+prop.getProperty("userName"));

输出结果:

userName&#xFF1A;&#x674E;&#x4E09;

3.4、使用 ResourceBundle 类加载文件

ResourceBundle 类加载文件,与 Properties 有所不同,ResourceBundle 获取 properties 文件不需要加 .properties后缀名,只需要文件名即可。

ResourceBundle 是按照iso8859编码格式来读取原属性文件,如果是读取中文内容,需要进行转码处理。

方法如下:

//加载custom配置文件,不需要加.properties后缀名
ResourceBundle resource = ResourceBundle.getBundle("custom");
//转码处理,解决读取中文内容乱码问题
String value = new String(resource.getString("userName").getBytes("ISO-8859-1"),"UTF-8");
System.out.println("userName:"+value);

输出结果:

userName:李三

四、总结

从源码上可以看出,Properties 继承自 Hashtable,大部分方法都复用于 Hashtable, 与 Hashtable 不同的是, Properties 中的 key 和 value 都是字符串。

实际开发中,Properties 主要用于读取配置文件,尤其是在不同的环境下,变量值需要不一样的情况,可以通过读取配置文件来 避免将变量值写死在 java 的枚举类中,以达到一行代码,多处运行的目的!

在读取 Properties 配置文件的时候,容易因文件路径找不到报错,可以参考 properties 文件加载的几种方式,如果网友还有新的加载方法,欢迎给我们留言!

五、参考

1、JDK1.7&JDK1.8 源码

2、CSDN – java读取properties配置文件的几种方法

Original: https://www.cnblogs.com/dxflqm/p/12022149.html
Author: 程序员志哥
Title: 深入浅出的分析 Properties

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/594229/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • fastposter v2.8.4 发布 电商海报生成器

    🔥🔥🔥 fastposter海报生成器,电商海报编辑器,电商海报设计器,fast快速生成海报 海报制作 海报开发。贰维🐴海报,图片海报,分享海报贰维🐴码推广海报,支持Java Py…

    Java 2023年6月5日
    093
  • Nginx的安装与运行

    前言:本文是基于虚拟机上的centOS 7对Nginx的安装,可以使用 uname -a查看centOS系统版本,本文用来记录安装nginx的步骤和相关命令,方便日后使用时查看。 …

    Java 2023年6月5日
    084
  • JVM学习笔记之垃圾收集算法【四】

    一、什么是垃圾回收? 垃圾回收(英语:Garbage Collection,缩写为 GC),在计算机科学中是一种自动的存储器管理机制。当一个电脑上的动态存储器不再需要时,就应该予以…

    Java 2023年6月5日
    0135
  • Redis进阶(一)

    通过简单的KV数据库理解Redis 分为访问模块,操作模块,索引模块,存储模块 底层数据结构 除了String类型,其他类型都是一个键对应一个集合,键值对的存储结构采用哈希表 哈希…

    Java 2023年6月8日
    0109
  • 基于XML的显式配置

    Spring提供了两种配置方式:一种是显式配置;一种是自动配置。显式配置又分为两种:一种是基于XML的显式配置;一种是基于Java的显式配置。自动配置只有一种,即基于注解的自动配置…

    Java 2023年6月5日
    099
  • Jdk内置的常用工具使用大全

    前言 此博文以jdk11中bin命令为准,旧版本被移除的命令不再介绍jdk的bin目录下面有许多命令,可以很方便的堆虚拟机进行监控或者故障排查等bin目录下的命令本质上都是Tool…

    Java 2023年5月30日
    068
  • Linux常用性能诊断命令详解

    top top命令动态地监视进程活动与系统负载等信息。 使用示例: top 效果如下图: 以上命令输出视图中分为两个区域,一个统计信息区,一个进程信息区。 统计信息区: 第一行信息…

    Java 2023年6月7日
    057
  • JAVA入门基础_从零开始的培训_Linux基础入门理解

    Linux操作系统 Linux操作系统的应用领域 VMware虚拟机的安装 在BIOS中开启操作系统的虚拟化 虚拟机的实际安装 Centos7.6版本的安装 下载Centos操作系…

    Java 2023年6月9日
    076
  • 玩转 SpringBoot2.x 之整合邮件发送

    序 在实际项目中,经常需要用到邮件通知功能。比如,用户通过邮件注册,通过邮件找回密码等;又比如通过邮件发送系统情况,通过邮件发送报表信息等等,实际应用场景很多。 原文地址:http…

    Java 2023年5月30日
    087
  • Django基础学习之Cookie 和 Sessions 应用

    在Django里面,使用Cookie和Session看起来好像是一样的,使用的方式都是request.COOKIES[XXX]和request.session[XXX],其中XXX…

    Java 2023年5月29日
    051
  • Netty

    学习本章需要先知道IO多路复用,不清楚的请移步:IO多路复用 网络通信中,阻塞IO两大阻塞的地方:socket链接阻塞,等待读取文件阻塞。 本地文件io就只有一个等待文件阻塞 一….

    Java 2023年6月7日
    0102
  • 妙用 Intellij IDEA 创建临时文件,Git 跟踪不到的那种

    | 好看请赞,养成习惯 你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT und…

    Java 2023年6月5日
    084
  • 接口回调与lambda表达式

    “接口回调”一词是借用了C语言中指针回调的术语,表示一个变量的地址在某一个时刻存放在一个指针变量中,那么指针变量就可以间接操作该变量中存放的数据。 和类一样…

    Java 2023年6月14日
    094
  • 全能成熟稳定开源分布式存储Ceph破冰之旅-上

    @ 概述 定义 传统存储方式及问题 优势 生产遇到问题 架构 总体架构 组成部分 CRUSH算法 数据读写过程 CLUSTER MAP 部署 部署建议 部署版本 部署方式 Ceph…

    Java 2023年6月5日
    0139
  • RocketMQ的push消费方式实现的太聪明了

    大家好,我是三友,我又来了~~ 最近仍然畅游在RocketMQ的源码中,这几天刚好翻到了消费者的源码,发现RocketMQ的对于push消费方式的实现简直太聪明了,所以趁着我脑子里…

    Java 2023年6月16日
    0126
  • ReentrantLock 公平锁源码 第2篇

    Reentrant 2 前两篇写完了后我自己研究了下,还有有很多疑惑和问题,这篇就继续以自问自答的方式写 如果没看过第1篇的可以先看看那个https://www.cnblogs.c…

    Java 2023年6月6日
    0112
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球