深入淺出的分析 Properties
最近在看 java 集合源碼的時候,發(fā)現(xiàn)原來我們經(jīng)常使用的 Properties 類既然繼承自 Hashtable!又漲見識了!
01. 摘要
在集合系列的第一章,咱們了解到,Map 的實現(xiàn)類有 HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties 等等。
在上一章節(jié)中,咱們介紹到 Hashtable 的數(shù)據(jù)結(jié)構(gòu)和算法實現(xiàn),在 Java 中其實還有一個非常重要的類 Properties,它繼承自 Hashtable,主要用于讀取配置文件。
本文通過看 JDK 和一些網(wǎng)友的博客總結(jié),主要從 Properties 的用法實例來做介紹,如果有理解不當之處,歡迎指正。
02. 簡介
Properties 類是 java 工具包中非常重要的一個類,比如在實際開發(fā)中,有些變量,我們可以直接硬寫入到自定義的 java 枚舉類中。
但是有些變量,在測試環(huán)境、預(yù)生產(chǎn)環(huán)境、生產(chǎn)環(huán)境,變量所需要取的值都不一樣,這個時候,我們可以通過使用 properties 文件來加載程序需要的配置信息,以達到一行代碼,多處環(huán)境都可以運行的效果!
最常見的比如 JDBC 數(shù)據(jù)源配置文件,properties文件以.properties作為后綴,文件內(nèi)容以鍵=值格式書寫,左邊是變量名稱,右邊是變量值,用#做注釋,比如新建一個jdbc.properties文件,內(nèi)容如下:
Properties 類是 properties 文件和程序的中間橋梁,不論是從 properties 文件讀取信息,還是寫入信息到 properties 文件,都要經(jīng)由 Properties 類。
好了,嘮叨了這么多,咱們回到本文要介紹的主角Properties!
從集合 Map 架構(gòu)圖可以看出,Properties 繼承自 Hashtable,表示一個持久的 map 集合,屬性列表以 key-value 的形式存在,Properties 類定義如下:
- public class Properties extends Hashtable<Object,Object> {
- ......
- }
Properties 除了繼承 Hashtable 中所定義的方法,Properties 也定義了以下幾個常用方法,如圖所示:
常用方法介紹
set 方法(添加修改元素)
set 方法是將指定的 key, value 對添加到 map 里,在添加元素的時候,調(diào)用了 Hashtable 的 put 方法,與 Hashtable 不同的是, key 和 value 都是字符串。
打開 Properties 的 setProperty 方法,源碼如下:
- public synchronized Object setProperty(String key, String value) {
- //調(diào)用父類 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());
- }
輸出結(jié)果:
{name3=張五, name2=張四, name1=張三}
get 方法(搜索指定元素)
get 方法根據(jù)指定的 key 值返回對應(yīng)的 value,第一步是從調(diào)用 Hashtable 的 get 方法,如果有返回值,直接返回;如果沒有返回值,但是初始化時傳入了defaults變量,從 defaults變量中,也就是 Properties 中,去搜索是否有對于的變量,如果有就返回元素值。
打開 Properties 的 getProperty 方法,源碼如下:
- public String getProperty(String key) {
- //調(diào)用父類 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;
- }
可以發(fā)現(xiàn),在 Properties 構(gòu)造方法初始化階段,如果你給了一個自定義的 defaults ,當調(diào)用 Hashtable 的 get 方法沒有搜索到元素值的時候,并且 defaults 也不等于空,那么就會進一步在 defaults 里面進行搜索元素值。
方法測試如下:
- public static void main(String[] args) {
- Properties properties = new Properties();
- properties.setProperty("name1","張三");
- properties.setProperty("name2","張四");
- properties.setProperty("name3","張五");
- //將 properties 作為參數(shù)初始化到 newProperties 中
- Properties newProperties = new Properties(properties);
- newProperties.setProperty("name4","李三");
- //查詢key中 name1 的值
- System.out.println("查詢結(jié)果:" + properties.getProperty("name1"));
- }
輸出結(jié)果:
通過key查詢結(jié)果:張三
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++;
- }
- //獲取文件中的鍵和值參數(shù)
- String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
- String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
- //調(diào)用 Hashtable 的 put 方法,將鍵值加入 map 中
- put(key, value);
- }
- }
好了,我們來在src/recources目錄下,新建一個custom.properties配置文件,內(nèi)容如下:
- #定義一個變量名稱和值
- 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"));
- //將內(nèi)容輸出到控制臺
- prop.list(System.out);
- }
- }
輸出結(jié)果:
userPwd=123456
userEmail=123@123.com
userAge=18
userName=李三
userGender=男
propertyNames方法(讀取全部信息)
propertyNames 方法,表示讀取 Properties 的全部信息,本質(zhì)是創(chuàng)建一個新的 Hashtable 對象,然后將原 Hashtable 中的數(shù)據(jù)復(fù)制到新的 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 中的數(shù)據(jù)添加到新的 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,防止內(nèi)容亂碼
- 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);
- }
- }
輸出內(nèi)容如下:
userPwd=123456
userEmail=123@123.com
userAge=18
userName=李三
userGender=男
總結(jié)
Properties 繼承自 Hashtable,大部分方法都復(fù)用于 Hashtable,比如,get、put、remove、clear 方法,**與 Hashtable 不同的是, Properties中的 key 和 value 都是字符串,**如果需要獲取 properties 中全部內(nèi)容,可以先通過迭代器或者 propertyNames 方法獲取 map 中所有的 key 元素,然后遍歷獲取 key 和 value。
需要注意的是,Properties 中的 setProperty 、load 方法,都加了synchronized同步鎖,用來控制線程同步。
03. properties 文件的加載方式
在實際開發(fā)中,經(jīng)常會遇到讀取配置文件路徑找不到,或者讀取文件內(nèi)容亂碼的問題,下面簡單介紹一下,properties 文件的幾種常用的加載方式。
properties 加載文件的方式,大致可以分兩類,第一類是使用 java.util.Properties 的 load 方法來加載文件流;第二類是使用 java.util.ResourceBundle 類來獲取文件內(nèi)容。
在src/recources目錄下,新建一個custom.properties配置文件,文件編碼格式為UTF-8,內(nèi)容還是以剛剛那個測試為例,各個加載方式如下!
通過文件路徑來加載文件
這類方法加載文件,主要是調(diào)用 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"));
輸出結(jié)果:
userName:李三
通過當前類加載器的getResourceAsStream方法獲取
這類方法加載文件,也是調(diào)用 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"));
輸出結(jié)果:
userName:李三
使用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"));
輸出結(jié)果:
userName:李三
使用 ResourceBundle 類加載文件
ResourceBundle 類加載文件,與 Properties 有所不同,ResourceBundle 獲取 properties 文件不需要加.properties后綴名,只需要文件名即可。
ResourceBundle 是按照iso8859編碼格式來讀取原屬性文件,如果是讀取中文內(nèi)容,需要進行轉(zhuǎn)碼處理。
方法如下:
- //加載custom配置文件,不需要加`.properties`后綴名
- ResourceBundle resource = ResourceBundle.getBundle("custom");
- //轉(zhuǎn)碼處理,解決讀取中文內(nèi)容亂碼問題
- String value = new String(resource.getString("userName").getBytes("ISO-8859-1"),"UTF-8");
- System.out.println("userName:"+value);
輸出結(jié)果:
userName:李三
04. 總結(jié)
從源碼上可以看出,Properties 繼承自 Hashtable,大部分方法都復(fù)用于 Hashtable,與 Hashtable 不同的是, Properties 中的 key 和 value 都是字符串。
實際開發(fā)中,Properties 主要用于讀取配置文件,尤其是在不同的環(huán)境下,變量值需要不一樣的情況,可以通過讀取配置文件來避免將變量值寫死在 java 的枚舉類中,以達到一行代碼,多處運行的目的!
在讀取 Properties 配置文件的時候,容易因文件路徑找不到報錯,可以參考 properties 文件加載的幾種方式,