作者:炸鸡可乐

原文出处:www.pzblog.cn

一、摘要

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

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

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

二、简介

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

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

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

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

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

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

  1. public class Properties extends Hashtable<Object,Object> {
  2. ......
  3. }

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

2.1、常用方法介绍

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

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

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

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

方法测试如下:

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

输出结果:

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

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

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

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

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

  1. public class Properties extends Hashtable<Object,Object> {
  2. protected Properties defaults;
  3. }

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

  1. public Properties(Properties defaults) {
  2. this.defaults = defaults;
  3. }

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

方法测试如下:

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

输出结果:

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

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

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

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

load0 方法,源码如下:

  1. private void load0 (LineReader lr) throws IOException {
  2. char[] convtBuf = new char[1024];
  3. int limit;
  4. int keyLen;
  5. int valueStart;
  6. char c;
  7. boolean hasSep;
  8. boolean precedingBackslash;
  9. //一行一行的读取
  10. while ((limit = lr.readLine()) >= 0) {
  11. c = 0;
  12. keyLen = 0;
  13. valueStart = limit;
  14. hasSep = false;
  15. precedingBackslash = false;
  16. //判断key的长度
  17. while (keyLen < limit) {
  18. c = lr.lineBuf[keyLen];
  19. if ((c == '=' || c == ':') && !precedingBackslash) {
  20. valueStart = keyLen + 1;
  21. hasSep = true;
  22. break;
  23. } else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) {
  24. valueStart = keyLen + 1;
  25. break;
  26. }
  27. if (c == '\\') {
  28. precedingBackslash = !precedingBackslash;
  29. } else {
  30. precedingBackslash = false;
  31. }
  32. keyLen++;
  33. }
  34. //获取值的起始位置
  35. while (valueStart < limit) {
  36. c = lr.lineBuf[valueStart];
  37. if (c != ' ' && c != '\t' && c != '\f') {
  38. if (!hasSep && (c == '=' || c == ':')) {
  39. hasSep = true;
  40. } else {
  41. break;
  42. }
  43. }
  44. valueStart++;
  45. }
  46. //获取文件中的键和值参数
  47. String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
  48. String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
  49. //调用 Hashtable 的 put 方法,将键值加入 map 中
  50. put(key, value);
  51. }
  52. }

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

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

方法测试如下:

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

输出结果:

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

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

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

  1. public Enumeration<?> propertyNames() {
  2. Hashtable<String,Object> h = new Hashtable<>();
  3. //将原 map 添加到新的 Hashtable 中
  4. enumerate(h);
  5. //返回 Hashtable 中全部的 key 元素
  6. return h.keys();
  7. }

enumerate 方法,源码如下:

  1. private synchronized void enumerate(Hashtable<String,Object> h) {
  2. //判断 Properties 中是否有初始化的配置文件
  3. if (defaults != null) {
  4. defaults.enumerate(h);
  5. }
  6. //将原 Hashtable 中的数据添加到新的 Hashtable 中
  7. for (Enumeration<?> e = keys() ; e.hasMoreElements() ;) {
  8. String key = (String)e.nextElement();
  9. h.put(key, get(key));
  10. }
  11. }

方法测试如下:

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

输出内容如下:

  1. userPwd=123456
  2. userEmail=123@123.com
  3. userAge=18
  4. userName=李三
  5. 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 方法,获取文件路径,读取文件以流的形式加载文件。

方法如下:

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

输出结果:

  1. userName:李三

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

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

方法如下:

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

输出结果:

  1. userName:李三

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

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

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

输出结果:

  1. userName:李三

3.4、使用 ResourceBundle 类加载文件

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

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

方法如下:

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

输出结果:

  1. userName:李三

四、总结

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

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

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

五、参考

1、JDK1.7&JDK1.8 源码

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

【集合系列】- 深入浅出的分析 Properties的更多相关文章

  1. 【集合系列】- 深入浅出的分析TreeMap

    一.摘要 在集合系列的第一章,咱们了解到,Map的实现类有HashMap.LinkedHashMap.TreeMap.IdentityHashMap.WeakHashMap.Hashtable.Pro ...

  2. 【集合系列】- 深入浅出的分析 Hashtable

    一.摘要 在集合系列的第一章,咱们了解到,Map 的实现类有 HashMap.LinkedHashMap.TreeMap.IdentityHashMap.WeakHashMap.Hashtable.P ...

  3. 【集合系列】- 深入浅出分析HashMap

    一.摘要 在集合系列的第一章,咱们了解到,Map的实现类有HashMap.LinkedHashMap.TreeMap.IdentityHashMap.WeakHashMap.Hashtable.Pro ...

  4. 【集合系列】- 深入浅出分析LinkedHashMap

    一.摘要 在集合系列的第一章,咱们了解到,Map的实现类有HashMap.LinkedHashMap.TreeMap.IdentityHashMap.WeakHashMap.Hashtable.Pro ...

  5. 【集合系列】- 深入浅出的分析IdentityHashMap

    一.摘要 在集合系列的第一章,咱们了解到,Map 的实现类有 HashMap.LinkedHashMap.TreeMap.IdentityHashMap.WeakHashMap.Hashtable.P ...

  6. 【集合系列】- 深入浅出的分析 WeakHashMap

    一.摘要 在集合系列的第一章,咱们了解到,Map 的实现类有 HashMap.LinkedHashMap.TreeMap.IdentityHashMap.WeakHashMap.Hashtable.P ...

  7. 【集合系列】- 深入浅出的分析 Set集合

    一.摘要 关于 Set 接口,在实际开发中,其实很少用到,但是如果你出去面试,它可能依然是一个绕不开的话题. 言归正传,废话咱们也不多说了,相信使用过 Set 集合类的朋友都知道,Set集合的特点主要 ...

  8. 【集合系列】- 深入浅出分析 ArrayDeque

    一.摘要 在 jdk1.5 中,新增了 Queue 接口,代表一种队列集合的实现,咱们继续来聊聊 java 集合体系中的 Queue 接口. Queue 接口是由大名鼎鼎的 Doug Lea 创建,中 ...

  9. Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)

    概要 前面,我们学完了List的全部内容(ArrayList, LinkedList, Vector, Stack). Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 Ja ...

随机推荐

  1. [ZJOI2013]K大数查询——整体二分

    题目描述 有N个位置,M个操作.操作有两种,每次操作如果是: 1 a b c:表示在第a个位置到第b个位置,每个位置加上一个数c 2 a b c:表示询问从第a个位置到第b个位置,第C大的数是多少. ...

  2. VM安装CentOS系统

    本篇文章主要介绍了VMware安装Centos7超详细过程(图文) 1.软硬件准备 软件:推荐使用VMwear,我用的是VMwear 12 镜像:CentOS7 ,如果没有镜像可以在官网下载 :htt ...

  3. 如何利用缓存机制实现JAVA类反射性能提升30倍

    一次性能提高30倍的JAVA类反射性能优化实践 文章来源:宜信技术学院 & 宜信支付结算团队技术分享第4期-支付结算部支付研发团队高级工程师陶红<JAVA类反射技术&优化> ...

  4. 『题解』洛谷P1083 借教室

    更好的阅读体验 Portal Portal1: Luogu Portal2: LibreOJ Portal3: Vijos Description 在大学期间,经常需要租借教室.大到院系举办活动,小到 ...

  5. Vue躬行记(8)——Vue Router

    虽然Vue.js未提供路由功能,但是官方推出了Vue Router(即vue-router库),以插件的形式支持.它与Vue.js深度集成,可快速的创建单页应用(Single Page Applica ...

  6. redis集群节点重启后恢复

    服务器重启后,集群报错: [root@SHH-HQ-NHS11S nhsuser]# redis-cli -c -h ip -p 7000ip:7000> set cc dd(error) CL ...

  7. go-micro+php+consul简单的微服实现

    首先我们用go-micro构建一个服务.(关于go-micro的使用可以参照官方实例或者文档) //新建一个微服务 micro new --type "srv" user-srv ...

  8. 构建 DNS 主从复制服务器

    一.主节点配置 1.yum install bind -y 安装 DNS 服务 2.vim /etc/named.conf 编辑 DNS 的配置文件 3.named-checkconf 检查配置文件 ...

  9. 领扣(LeetCode)二叉树的右视图 个人题解

    给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值. 示例: 输入: [1,2,3,null,5,null,4] 输出: [1, 3, 4] 解释: 1 < ...

  10. python:timeit模块

    (鱼c)timeit模块详解——准确测量小段代码的执行时间 http://bbs.fishc.com/forum.php?mod=viewthread&tid=55593&extra= ...