1. 程式人生 > >【集合系列】- 深入淺出的分析 Properties

【集合系列】- 深入淺出的分析 Properties

一、摘要

在集合系列的第一章,咱們瞭解到,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 類定義如下:

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配置檔案的幾種方法

作者:炸雞可樂
原文出處:www.pzblog.cn