一文帶你全面了解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)
當(dāng)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;
// 當(dāng)前行信息的緩沖區(qū),大小為1024個字符
char[] lineBuf = new char[1024];
// 讀取一行數(shù)據(jù)時候的實際讀取大小
int inLimit = 0;
// 讀取的時候指向當(dāng)前字符位置
int inOff = 0;
// 字節(jié)流對象
InputStream inStream;
// 字符流對象
Reader reader;
/**
* 讀取一行,將行信息保存到{@link lineBuf}對象中,并返回實際的字符個數(shù)
*
* @return 實際讀取的字符個數(shù)
* @throws IOException
*/
int readLine() throws IOException {
// 總的字符長度
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ù),并返回這一行的實際讀取大小
inLimit = (inStream == null) ? reader.read(inCharBuf) : inStream.read(inByteBuf);
inOff = 0;
// 如果沒有讀取到數(shù)據(jù),那么就直接結(jié)束讀取操作
if (inLimit <= 0) {
// 如果當(dāng)前長度為0或者是改行是注釋,那么就返回-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進行編碼的,所以在這里進行解碼操作。
c = (char) (0xff & inByteBuf[inOff++]);
} else {
c = inCharBuf[inOff++];
}
// 如果前一個字符是換行符號,那么判斷當(dāng)前字符是否也是換行符號
if (skipLF) {
skipLF = false;
if (c == '\n') {
continue;
}
}
// 如果前一個字符是空格,那么判斷當(dāng)前字符是不是空格類字符
if (skipWhiteSpace) {
if (c == ' ' || c == '\t' || c == '\f') {
continue;
}
if (!appendedLineBegin && (c == '\r' || c == '\n')) {
continue;
}
skipWhiteSpace = false;
appendedLineBegin = false;
}
// 如果當(dāng)前新的一行,那么進入該if判斷中
if (isNewLine) {
isNewLine = false;
// 如果當(dāng)前字符是#或者是!,那么表示該行是一個注釋行
if (c == '#' || c == '!') {
isCommentLine = true;
continue;
}
}
// 根據(jù)當(dāng)前字符是不是換行符號進行判斷操作
if (c != '\n' && c != '\r') {
// 當(dāng)前字符不是換行符號
lineBuf[len++] = c;// 將當(dāng)前字符寫入到行信息緩沖區(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) {
// 如果這一行是注釋行,或者是當(dāng)前長度為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;
// 當(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的長度
keyLen = 0;
// value的起始位置默認為limit
valueStart = limit;
//
hasSep = false;
precedingBackslash = false;
// 如果key的長度小于總的字符長度,那么就進入循環(huán)
while (keyLen < limit) {
// 獲取當(dāng)前字符
c = lr.lineBuf[keyLen];
// 如果當(dāng)前字符是=或者是:,而且前一個字符不是轉(zhuǎn)義字符,那么就表示key的描述已經(jīng)結(jié)束
if ((c == '=' || c == ':') && !precedingBackslash) {
// 指定value的起始位置為當(dāng)前keyLen的下一個位置
valueStart = keyLen + 1;
// 并且指定,去除空格
hasSep = true;
break;
} else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) {
// 如果當(dāng)前字符是空格類字符,而且前一個字符不是轉(zhuǎn)義字符,那么表示key的描述已經(jīng)結(jié)束
// 指定value的起始位置為當(dāng)前位置的下一個位置
valueStart = keyLen + 1;
break;
}
// 如果當(dāng)前字符為'',那么跟新是否是轉(zhuǎn)義號。
if (c == '\') {
precedingBackslash = !precedingBackslash;
} else {
precedingBackslash = false;
}
keyLen++;
}
// 如果value的起始位置小于總的字符長度,那么就進入該循環(huán)
while (valueStart < limit) {
// 獲取當(dāng)前字符
c = lr.lineBuf[valueStart];
// 判斷當(dāng)前字符是否是空格類字符,達到去空格的效果
if (c != ' ' && c != '\t' && c != '\f') {
// 當(dāng)前字符不是空格類字符,而且當(dāng)前字符為=或者是:,并在此之前沒有出現(xiàn)過=或者:字符。
// 那么value的起始位置繼續(xù)往后移動。
if (!hasSep && (c == '=' || c == ':')) {
hasSep = true;
} else {
// 當(dāng)前字符不是=或者:,或者在此之前出現(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);
}
}會將分割符號兩邊的空格去掉,并且分割符號可以是=,:,空格等。而且=和:的級別比空格分隔符高,即當(dāng)這兩個都存在的情況下,是按照=/:分割的??梢钥吹皆谧詈髸{(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-08
MyBatis中SqlSession實現(xiàn)增刪改查案例
這篇文章主要介紹了MyBatis中SqlSession實現(xiàn)增刪改查案例,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-03-03
Spring SpringMVC在啟動完成后執(zhí)行方法源碼解析
這篇文章主要介紹了SpringMVC在啟動完成后執(zhí)行方法源碼解析,還是非常不錯的,在這里分享給大家,需要的朋友可以參考下。2017-09-09

