一文帶你全面了解Java?Properties類
概述
Properties是JDK1.0中引入的java類,目前也在項目中大量使用,主要用來讀取外部的配置,那除了這個,你對它其他的一些api也了解嗎? 你了解它是怎么實現(xiàn)的嗎? 如果不清楚的話,就通過本篇文章帶你一探究竟。
介紹
java.util.Properties繼承自java.util.Hashtable,是一個持久化的屬性保存對象,可以將屬性內(nèi)容寫出到stream中或者從stream中讀取屬性內(nèi)容。 它的重要特性如下:
- 在底層的Hashtable中,每一對屬性的key和value都是按照string類型來保存的。
- Properties支持文本方式和xml方式的數(shù)據(jù)存儲。在文本方式中,格式為key:value,其中分隔符可以是:冒號(:)、等號(=)、空格。其中空格可以作為key的結(jié)束,同時獲取的值回將分割符號兩端的空格去掉。
- Properties可以將其他的Properties對象作為默認的值。
- Hashtable的所有方法Properties對象均可以訪問,但是不建議這么做,因為Hashtable可以存放其他數(shù)據(jù)類型,這樣會導(dǎo)致Properties一些方法調(diào)用報錯。
- 在properties文件中,可以用井號"#"來作注釋。
- 線程安全
- key、value不可以是null
構(gòu)造方法
Properties()
創(chuàng)建一個無默認值的空屬性列表。
Properties(Properties defaults)
創(chuàng)建一個帶有指定默認值的空屬性列表。
關(guān)鍵方法
getProperty ( String key)
根據(jù)指定的key獲取對應(yīng)的屬性value值,如果在自身的存儲集合中沒有找到對應(yīng)的key,那么就直接到默認的defaults屬性指定的Properties中獲取屬性值。
getProperty(String, String)
當getProperty(String)方法返回值為null的時候,返回給定的默認值,而不是返回null。
load ( InputStream inStream)
從byte stream中加載key/value鍵值對,要求所有的key/value鍵值對是按行存儲,同時是用ISO-8859-1編譯的, 不支持中文。
load(Reader)
從字符流中加載key/value鍵值對,要求所有的鍵值對都是按照行來存儲的。
loadFromXML(InputStream)
從xml文件中加載property,底層使用XMLUtils.load(Properties,InputStream)方法來加載。
setProperty ( String key, String value)
調(diào)用 Hashtable 的方法 put 。他通過調(diào)用基類的put方法來設(shè)置 鍵 - 值對。
store ( OutputStream out, String comments)
將所有的property(保存defaults的)都寫出到流中,同時如果給定comments的話,那么要加一個注釋。
storeToXML(OutputSteam, comment, encoding)
寫出到xml文件中。
Set stringPropertyNames()
獲取所有Properties中所有的key集合
clear ()
清除所有裝載的 鍵值對。該方法在基類中提供。
使用案例
新建配置文件app.properties
## 用戶信息 user.name:旭陽 user.age=28 user.sex 男
通過idea設(shè)置它的格式為UTF-8。
驗證讀取以及中文亂碼的問題
@Test public void test1() throws IOException { Properties properties = new Properties(); // 使用load inputstream properties.load(this.getClass().getClassLoader().getResourceAsStream("app.properties")); // 出現(xiàn)亂碼 System.out.println(properties); // 轉(zhuǎn)碼 System.out.println(new String(properties.getProperty("user.name").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); Properties properties2 = new Properties(); // 使用load read BufferedReader bf = new BufferedReader(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("app.properties"), "UTF-8")); properties2.load(bf); System.out.println(properties2); }
運行結(jié)果:
保存為xml格式
@Test public void test2() throws IOException { Properties properties2 = new Properties(); // 使用load read BufferedReader bf = new BufferedReader(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("app.properties"), "UTF-8")); properties2.load(bf); System.out.println(properties2); // 保存到xml FileOutputStream fileOutputStream = new FileOutputStream("app.xml"); properties2.storeToXML(fileOutputStream, "alvin info", "UTF-8"); }
運行結(jié)果:
源碼解析
源碼這部分主要分析下load(Reader)和load(InputStream)這兩個最常用的方法,這兩個方法是指定從文本文件中加載key/value屬性值,底層都是將流封裝成為LineReader對象,然后通過load0方法來加載屬性鍵值對的。
public synchronized void load(InputStream inStream) throws IOException { load0(new LineReader(inStream)); }
將inputStream封裝程一個LineReader,每次可以讀取一行數(shù)據(jù)。
LineReader源碼分析:
class LineReader { /** * 根據(jù)字節(jié)流創(chuàng)建LineReader對象 * * @param inStream * 屬性鍵值對對應(yīng)的字節(jié)流對象 */ public LineReader(InputStream inStream) { this.inStream = inStream; inByteBuf = new byte[8192]; } /** * 根據(jù)字符流創(chuàng)建LineReader對象 * * @param reader * 屬性鍵值對對應(yīng)的字符流對象 */ public LineReader(Reader reader) { this.reader = reader; inCharBuf = new char[8192]; } // 字節(jié)流緩沖區(qū), 大小為8192個字節(jié) byte[] inByteBuf; // 字符流緩沖區(qū),大小為8192個字符 char[] inCharBuf; // 當前行信息的緩沖區(qū),大小為1024個字符 char[] lineBuf = new char[1024]; // 讀取一行數(shù)據(jù)時候的實際讀取大小 int inLimit = 0; // 讀取的時候指向當前字符位置 int inOff = 0; // 字節(jié)流對象 InputStream inStream; // 字符流對象 Reader reader; /** * 讀取一行,將行信息保存到{@link lineBuf}對象中,并返回實際的字符個數(shù) * * @return 實際讀取的字符個數(shù) * @throws IOException */ int readLine() throws IOException { // 總的字符長度 int len = 0; // 當前字符 char c = 0; boolean skipWhiteSpace = true; boolean isCommentLine = false; boolean isNewLine = true; boolean appendedLineBegin = false; boolean precedingBackslash = false; boolean skipLF = false; while (true) { if (inOff >= inLimit) { // 讀取一行數(shù)據(jù),并返回這一行的實際讀取大小 inLimit = (inStream == null) ? reader.read(inCharBuf) : inStream.read(inByteBuf); inOff = 0; // 如果沒有讀取到數(shù)據(jù),那么就直接結(jié)束讀取操作 if (inLimit <= 0) { // 如果當前長度為0或者是改行是注釋,那么就返回-1。否則返回len的值。 if (len == 0 || isCommentLine) { return -1; } return len; } } // 判斷是根據(jù)字符流還是字節(jié)流讀取當前字符 if (inStream != null) { // The line below is equivalent to calling a ISO8859-1 decoder. // 字節(jié)流是根據(jù)ISO8859-1進行編碼的,所以在這里進行解碼操作。 c = (char) (0xff & inByteBuf[inOff++]); } else { c = inCharBuf[inOff++]; } // 如果前一個字符是換行符號,那么判斷當前字符是否也是換行符號 if (skipLF) { skipLF = false; if (c == '\n') { continue; } } // 如果前一個字符是空格,那么判斷當前字符是不是空格類字符 if (skipWhiteSpace) { if (c == ' ' || c == '\t' || c == '\f') { continue; } if (!appendedLineBegin && (c == '\r' || c == '\n')) { continue; } skipWhiteSpace = false; appendedLineBegin = false; } // 如果當前新的一行,那么進入該if判斷中 if (isNewLine) { isNewLine = false; // 如果當前字符是#或者是!,那么表示該行是一個注釋行 if (c == '#' || c == '!') { isCommentLine = true; continue; } } // 根據(jù)當前字符是不是換行符號進行判斷操作 if (c != '\n' && c != '\r') { // 當前字符不是換行符號 lineBuf[len++] = c;// 將當前字符寫入到行信息緩沖區(qū)中,并將len自增加1. // 如果len的長度大于行信息緩沖區(qū)的大小,那么對lineBuf進行擴容,擴容大小為原來的兩倍,最大為Integer.MAX_VALUE if (len == lineBuf.length) { int newLength = lineBuf.length * 2; if (newLength < 0) { newLength = Integer.MAX_VALUE; } char[] buf = new char[newLength]; System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length); lineBuf = buf; } // 是否是轉(zhuǎn)義字符 // flip the preceding backslash flag if (c == '\') { precedingBackslash = !precedingBackslash; } else { precedingBackslash = false; } } else { // reached EOL if (isCommentLine || len == 0) { // 如果這一行是注釋行,或者是當前長度為0,那么進行clean操作。 isCommentLine = false; isNewLine = true; skipWhiteSpace = true; len = 0; continue; } // 如果已經(jīng)沒有數(shù)據(jù)了,就重新讀取 if (inOff >= inLimit) { inLimit = (inStream == null) ? reader.read(inCharBuf) : inStream.read(inByteBuf); inOff = 0; if (inLimit <= 0) { return len; } } // 查看是否是轉(zhuǎn)義字符 if (precedingBackslash) { // 如果是,那么表示是另起一行,進行屬性的定義,len要自減少1. len -= 1; // skip the leading whitespace characters in following line skipWhiteSpace = true; appendedLineBegin = true; precedingBackslash = false; if (c == '\r') { skipLF = true; } } else { return len; } } } } }
readLine這個方法每次讀取一行數(shù)據(jù);如果我們想在多行寫數(shù)據(jù),那么可以使用''來進行轉(zhuǎn)義,在該轉(zhuǎn)義符號后面換行,是被允許的。
load0方法源碼如下:
private void load0(LineReader lr) throws IOException { char[] convtBuf = new char[1024]; // 讀取的字符總數(shù) int limit; // 當前key所在位置 int keyLen; // value的起始位置 int valueStart; // 當前字符 char c; // boolean hasSep; // 是否是轉(zhuǎn)義字符 boolean precedingBackslash; while ((limit = lr.readLine()) >= 0) { c = 0; // key的長度 keyLen = 0; // value的起始位置默認為limit valueStart = limit; // hasSep = false; precedingBackslash = false; // 如果key的長度小于總的字符長度,那么就進入循環(huán) while (keyLen < limit) { // 獲取當前字符 c = lr.lineBuf[keyLen]; // 如果當前字符是=或者是:,而且前一個字符不是轉(zhuǎn)義字符,那么就表示key的描述已經(jīng)結(jié)束 if ((c == '=' || c == ':') && !precedingBackslash) { // 指定value的起始位置為當前keyLen的下一個位置 valueStart = keyLen + 1; // 并且指定,去除空格 hasSep = true; break; } else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { // 如果當前字符是空格類字符,而且前一個字符不是轉(zhuǎn)義字符,那么表示key的描述已經(jīng)結(jié)束 // 指定value的起始位置為當前位置的下一個位置 valueStart = keyLen + 1; break; } // 如果當前字符為'',那么跟新是否是轉(zhuǎn)義號。 if (c == '\') { precedingBackslash = !precedingBackslash; } else { precedingBackslash = false; } keyLen++; } // 如果value的起始位置小于總的字符長度,那么就進入該循環(huán) while (valueStart < limit) { // 獲取當前字符 c = lr.lineBuf[valueStart]; // 判斷當前字符是否是空格類字符,達到去空格的效果 if (c != ' ' && c != '\t' && c != '\f') { // 當前字符不是空格類字符,而且當前字符為=或者是:,并在此之前沒有出現(xiàn)過=或者:字符。 // 那么value的起始位置繼續(xù)往后移動。 if (!hasSep && (c == '=' || c == ':')) { hasSep = true; } else { // 當前字符不是=或者:,或者在此之前出現(xiàn)過=或者:字符。那么結(jié)束循環(huán)。 break; } } valueStart++; } // 讀取key String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); // 讀取value String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); // 包括key/value put(key, value); } }
會將分割符號兩邊的空格去掉,并且分割符號可以是=,:,空格等。而且=和:的級別比空格分隔符高,即當這兩個都存在的情況下,是按照=/:分割的。可以看到在最后會調(diào)用一個loadConvert方法,該方法主要是做key/value的讀取,并將十六進制的字符進行轉(zhuǎn)換。
總結(jié)
本文闡述了Properties的基本作用以及源碼實現(xiàn),是不是對Properties有了更近一步的認識呢。
以上就是一文帶你全面了解Java Properties類的詳細內(nèi)容,更多關(guān)于Java Properties類的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot使用Flyway進行數(shù)據(jù)庫遷移的實現(xiàn)示例
Flyway是一個數(shù)據(jù)庫遷移工具,它提供遷移歷史和回滾的功能,本文主要介紹了如何使用Flyway來管理Spring Boot應(yīng)用程序中的SQL數(shù)據(jù)庫架構(gòu),感興趣的可以了解一下2023-08-08MyBatis中SqlSession實現(xiàn)增刪改查案例
這篇文章主要介紹了MyBatis中SqlSession實現(xiàn)增刪改查案例,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-03-03Spring SpringMVC在啟動完成后執(zhí)行方法源碼解析
這篇文章主要介紹了SpringMVC在啟動完成后執(zhí)行方法源碼解析,還是非常不錯的,在這里分享給大家,需要的朋友可以參考下。2017-09-09