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