最近賦閑在家閑的蛋疼,找工作也不順利,就安靜下來學(xué)一些常用開源項目,在翻struts2的時候看到讀取properties配置文件是自己定義的reader來讀取,因為之前上班的時候常常使用到properties的讀寫,對于jdk本身的properties在保存的時候會把注釋忽略掉這點深惡痛絕,一直想重新寫一個properties文件讀寫的工具類,但是大致翻了一下properties的代碼和文檔,發(fā)現(xiàn)properties的規(guī)則挺多,沒有幾天時間怕是難以完成就一直擱下了。這次看到struts2的代碼就想拿來借鑒一下,于是就把properties的東西讀了一遍,發(fā)覺很多東西是之前忽略甚至不知道的,于是記下和兄弟們共享,如有錯歡迎指正,概念頗多,容易暈頭,建議找頭腦清醒的時候看。
JDK Properties核心在讀取配置文件的
private void load0 (LineReader lr) throws IOException方法上。其中傳入的參數(shù)LineReader類是Properties的內(nèi)部類,用來讀取一個邏輯行(這兒就不詳細介紹了,它會讀取一個邏輯行并且忽略掉邏輯行行首的所有空白字符和換行字符)。 load0方法的JDK文檔總結(jié)如下,這也是后續(xù)的幾個重要的概念的出處: 1.注釋符為:'#'或者'!'。空白字符為:' ', '\t', '\f'。key/value分隔符為:'='或者':'。行分隔符為:'\r','\n','\r\n'。 2.自然行是使用行分隔符或者流的結(jié)尾來分割的行。邏輯行可能分割到多個自然行中,使用反斜杠'\'來連接多個自然行。 3.注釋行是使用注釋符作為首個非空白字符的自然行。 4.空白字符的自然行會被認為是空行而被忽略。 5.properties文件的key為從首個非空白字符開始直到(但不包括)首個非轉(zhuǎn)義的'=', ':'或者非行結(jié)束符的空白字符為止。 6.key后面的第一個非空白字符如果是”=”或者”:”,那么這個字符后面所有空白字符都會被忽略掉。 7.可以使用轉(zhuǎn)義序列表示key和value(當(dāng)然此處的字符轉(zhuǎn)義序列和unicode的轉(zhuǎn)義有一些差別,jdk文檔都有列出來)。 properties是一個包含了key、value對的文本文檔,key,value的界定是正確讀取properties的關(guān)鍵,那么key、value是如何界定的呢?上面第5點是對key的不完全界定但是并未涉及到value,這些,都只有從源碼當(dāng)中來尋找答案。 load0源碼和注解如下:
private void load0(LineReader lr) throws IOException { char[] convtBuf = new char[1024]; //行的長度 int limit; //key的長度 int keyLen; //value的開始點 int valueStart; //當(dāng)前讀取的字符 char c; //是否是key/value的分隔符 boolean hasSep; //前一個字符是否是反斜杠 boolean precedingBackslash; //把通過LineReader讀取來的邏輯行進行遍歷,一個個char的進行處理。 while ((limit = lr.readLine()) >= 0) { c = 0; keyLen = 0; valueStart = limit; hasSep = false; precedingBackslash = false; //循環(huán)獲取key的長度 while (keyLen < limit) { c = lr.lineBuf[keyLen]; //當(dāng)字符為key/value分隔符:'='或':'并且前一個字符不是反斜杠的時候,key長度讀取結(jié)束,并且把hasSep設(shè)置為true,break。 if ((c == '=' || c == ':') && !precedingBackslash) { valueStart = keyLen + 1; hasSep = true; break; } //當(dāng)字符為空白字符' '或'\t'或'\f'并且前一個字符不是反斜杠的時候,key長度讀取結(jié)束,break。 else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { valueStart = keyLen + 1; break; } //當(dāng)連續(xù)存在奇數(shù)個反斜杠的時候, precedingBackslash為true。 if (c == '\\') { precedingBackslash = !precedingBackslash; } else { precedingBackslash = false; } keyLen++; } //循環(huán)獲取value開始的位置 while (valueStart < limit) { c = lr.lineBuf[valueStart]; //如果字符不為所有的空白字符:' ', '\t', '\f'的時候 if (c != ' ' && c != '\t' && c != '\f') { //如果前面不是key/value的分隔符,而是空白字符,而該字符是key/value分隔符 if (!hasSep && (c == '=' || c == ':')) { hasSep = true; } else { //結(jié)束讀取 break; } } valueStart++; } //loadConvert是進行字符串轉(zhuǎn)義的方法,就不用關(guān)心了。 String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); put(key, value); } } 通過如上的代碼可以看出,key/value分割符'=', ':'與空白字符:' ', '\t', '\f'是區(qū)分key、value的關(guān)鍵: key的界定為:邏輯行中,從首個非空白字符開始直到(但不包括)首個非轉(zhuǎn)義的'=', ':'或者非行結(jié)束符的空白字符為止。(和前面第5點基本一致) value的界定為:邏輯行中,非轉(zhuǎn)義的key/value分隔符(此處不僅僅包括'=',':',還包括' ', '\t', '\f')后面的第一個非空白字符(非' ', '\t', '\f'字符)開始到邏輯行結(jié)束的所有字符。 另外key、value還有如下特征: 1.因為LineReader是讀取的邏輯行,所以key、value中可以包含多個自然行。 2.在“循環(huán)獲取key的長度”的代碼中可以看到處理key/value分隔符的方式和處理空白字符的方式很相似(除了在發(fā)現(xiàn)處理的字符為key/value分隔符的時候會把 hasSep變量設(shè)置為true)。而這表明: 如果空白字符后續(xù)沒有key/value分隔符(“=”或者“:”),那么該空白字符會被當(dāng)作key/value分隔符,從分隔符后的第一個非空白字符起到邏輯行結(jié)束所有的字符都當(dāng)作是value。也就是說:“key1 value1”,讀取出來之后的key和value分別為”key1”, “value1”。 如果空白字符后續(xù)有key/value分隔符(“=”或者“:”),那么該空白字符會被忽略,key/value分隔符后的第一個非空白字符起到邏輯行結(jié)束所有的字符都當(dāng)作是value。也就是說:”key1 :value1”,讀取出來之后的key和value分別為”key1”和”value1”,而不是”key1”和”:value1”。 另外,在讀xwork的com.opensymphony.xwork2.util.PropertiesReader類的時候發(fā)現(xiàn),它的實現(xiàn)和JDK的Properties實現(xiàn)有出入,也就是說,如果JDK的Properties是規(guī)范的話,那么xwork的properties讀取類是有bug的。測試類如下(注釋掉的Assert才能通過junit):
public class PropertiesTest { @Test public void testLoad() throws IOException { File f = new File(getClass().getResource(".").getPath(), "test.properties"); InputStream in = null; try { //java properties in = new FileInputStream(f); Properties props = new Properties(); props.load(in); String s1 = props.getProperty("key"); Assert.assertEquals("value#with", s1); String s2 = props.getProperty("comments"); Assert.assertEquals("", s2); } finally { if (in != null) try { in.close(); } catch (IOException e) { e.printStackTrace(); } } try { //xwork properties in = new FileInputStream(f); Reader reader = new InputStreamReader(in); PropertiesReader pr = new PropertiesReader(reader); while (pr.nextProperty()) { String name = pr.getPropertyName(); String val = pr.getPropertyValue(); if ("key".equals(name)) { Assert.assertEquals("value#with", val); //Assert.assertEquals("valuecomments", val); } if ("comments".equals(name)) { Assert.assertEquals("", val); //Assert.assertEquals(null, val); } } } finally { if (in != null) try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } } test.properties的內(nèi)容如下:
key=value#with comments 好了,清楚properties的使用規(guī)則了,如果我們需要自己寫一個實現(xiàn)在保存properties的時候注釋不被忽略掉,而且按照原來的行數(shù)來保存的工具類的話,就會清晰很多了。本來想把這個工具寫一下,但是寫代碼加調(diào)試實在太費時間,等到用的時候再來寫吧。 |
|
來自: squarecome > 《我的圖書館》