[Collection] series - Analysis Properties in simple terms

I. Summary

In the first chapter of a collection series, we learned, Map implementation class has HashMap, LinkedHashMap, TreeMap, IdentityHashMap, WeakHashMap, Hashtable, Properties and so on.

In the previous chapter, we introduce the Hashtable data structures and algorithms in Java, there is also a very important class Properties, which inherits from Hashtable, mainly used to read the configuration file.

By looking JDK and some users blog summary, mainly from the use of Properties to make an introduction instance, if there is inappropriate understanding, please correct me.

Second, Introduction

Properties class is very important java toolkit in a class, such as in the actual development, some of the variables, we can write directly to the hard java enum class in a custom.

But some variables are not the same value in the test environment, pre-production environment, production environment, the need to take the variables , this time, we can load configuration information needed by the program using properties files, in order to achieve a line of code, many environment both effects can run!

The most common such as JDBC data source configuration file, propertiesfile .propertiesas a suffix, file contents 键=值written in the format, the left is the variable name, the right is the value of the variable, use #make notes, for example, create a new jdbc.propertiesfile, as follows:

Properties middle class is a bridge properties files and programs, whether the information read from the properties file, or write information to the properties file, to be via the Properties class.

Well, nagging so much, we returned to the protagonist of this article to introduce the Properties !

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

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

Properties 除了继承 Hashtable 中所定义的方法,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<Object,Object> {
    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=男
[email protected]

方法测试如下:

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
[email protected]
userAge=18
userName=李三
userGender=男
2.1.4、propertyNames方法(读取全部信息)

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

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

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

enumerate 方法,源码如下:

private synchronized void enumerate(Hashtable<String,Object> 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
[email protected]
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:李三

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:李三

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:李三

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配置文件的几种方法

Guess you like

Origin www.cnblogs.com/dxflqm/p/12022149.html