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