锁定老帖子 主题:正确使用Java Properties
该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间:2011-01-18
最后修改:2011-01-19
JDK Properties核心在读取配置文件的 private void load0 (LineReader lr) throws IOException方法上。其中传入的参数LineReader类是Properties的内部类,用来读取一个逻辑行(这儿就不详细介绍了,它会读取一个逻辑行并且忽略掉逻辑行行首的所有空白字符和换行字符)。 load0方法的JDK文档总结如下,这也是后续的几个重要的概念的出处: 1.注释符为:'#'或者'!'。空白字符为:' ', '\t', '\f'。key/value分隔符为:'='或者':'。行分隔符为:'\r','\n','\r\n'。 2.自然行是使用行分隔符或者流的结尾来分割的行。逻辑行可能分割到多个自然行中,使用反斜杠'\'来连接多个自然行。 3.注释行是使用注释符作为首个非空白字符的自然行。 4.空白字符的自然行会被认为是空行而被忽略。 5.properties文件的key为从首个非空白字符开始直到(但不包括)首个非转义的'=', ':'或者非行结束符的空白字符为止。 6.key后面的第一个非空白字符如果是”=”或者”:”,那么这个字符后面所有空白字符都会被忽略掉。 7.可以使用转义序列表示key和value(当然此处的字符转义序列和unicode的转义有一些差别,jdk文档都有列出来)。 properties是一个包含了key、value对的文本文档,key,value的界定是正确读取properties的关键,那么key、value是如何界定的呢?上面第5点是对key的不完全界定但是并未涉及到value,这些,都只有从源码当中来寻找答案。 load0源码和注解如下: private void load0(LineReader lr) throws IOException { char[] convtBuf = new char[1024]; //行的长度 int limit; //key的长度 int keyLen; //value的开始点 int valueStart; //当前读取的字符 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; //循环获取key的长度 while (keyLen < limit) { c = lr.lineBuf[keyLen]; //当字符为key/value分隔符:'='或':'并且前一个字符不是反斜杠的时候,key长度读取结束,并且把hasSep设置为true,break。 if ((c == '=' || c == ':') && !precedingBackslash) { valueStart = keyLen + 1; hasSep = true; break; } //当字符为空白字符' '或'\t'或'\f'并且前一个字符不是反斜杠的时候,key长度读取结束,break。 else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { valueStart = keyLen + 1; break; } //当连续存在奇数个反斜杠的时候, precedingBackslash为true。 if (c == '\\') { precedingBackslash = !precedingBackslash; } else { precedingBackslash = false; } keyLen++; } //循环获取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 { //结束读取 break; } } valueStart++; } //loadConvert是进行字符串转义的方法,就不用关心了。 String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); put(key, value); } } 通过如上的代码可以看出,key/value分割符'=', ':'与空白字符:' ', '\t', '\f'是区分key、value的关键: key的界定为:逻辑行中,从首个非空白字符开始直到(但不包括)首个非转义的'=', ':'或者非行结束符的空白字符为止。(和前面第5点基本一致) value的界定为:逻辑行中,非转义的key/value分隔符(此处不仅仅包括'=',':',还包括' ', '\t', '\f')后面的第一个非空白字符(非' ', '\t', '\f'字符)开始到逻辑行结束的所有字符。 另外key、value还有如下特征: 1.因为LineReader是读取的逻辑行,所以key、value中可以包含多个自然行。 2.在“循环获取key的长度”的代码中可以看到处理key/value分隔符的方式和处理空白字符的方式很相似(除了在发现处理的字符为key/value分隔符的时候会把 hasSep变量设置为true)。而这表明: 如果空白字符后续没有key/value分隔符(“=”或者“:”),那么该空白字符会被当作key/value分隔符,从分隔符后的第一个非空白字符起到逻辑行结束所有的字符都当作是value。也就是说:“key1 value1”,读取出来之后的key和value分别为”key1”, “value1”。 如果空白字符后续有key/value分隔符(“=”或者“:”),那么该空白字符会被忽略,key/value分隔符后的第一个非空白字符起到逻辑行结束所有的字符都当作是value。也就是说:”key1 :value1”,读取出来之后的key和value分别为”key1”和”value1”,而不是”key1”和”:value1”。 另外,在读xwork的com.opensymphony.xwork2.util.PropertiesReader类的时候发现,它的实现和JDK的Properties实现有出入,也就是说,如果JDK的Properties是规范的话,那么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的内容如下: key=value\ #with comments 好了,清楚properties的使用规则了,如果我们需要自己写一个实现在保存properties的时候注释不被忽略掉,而且按照原来的行数来保存的工具类的话,就会清晰很多了。本来想把这个工具写一下,但是写代码加调试实在太费时间,等到用的时候再来写吧。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2011-01-18
写完了,蛋。。真的很疼…………
|
|
返回顶楼 | |
发表时间:2011-01-19
都是读properties,还没写过
|
|
返回顶楼 | |
发表时间:2011-01-19
marmot 写道 真的是蛋疼
囧,如果你需要的深入了解properties规范的时候可以回来翻翻。我写这个文章的原因就是我没有找到过完整记录properties规则的文章,让我没有办法去对jdk的properties类进行扩展。 就像验证IPV4的IP地址一样,如果不熟悉IP地址的规则,用正则表达式傻乎乎的验证四段数字,会被人鄙视的。 |
|
返回顶楼 | |
发表时间:2011-01-19
http://www.iteye.com/topic/156474
这里还有个好东东 我上次在项目中使用了 |
|
返回顶楼 | |
发表时间:2011-01-19
weilJava 写道 http://www.iteye.com/topic/156474
这里还有个好东东 我上次在项目中使用了 挺不错,大致看了貌似对properties也是做了不错的总结然后自己做的一个封装。。 |
|
返回顶楼 | |
发表时间:2011-01-19
hobitton 写道 就像验证IPV4的IP地址一样,如果不熟悉IP地址的规则,用正则表达式傻乎乎的验证四段数字,会被人鄙视的。
为什么正则不能验证IPV4,很想知道哪里傻乎 (?<![\\d\\.])((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)(?![\\d\\.]) |
|
返回顶楼 | |
发表时间:2011-01-19
最后修改:2011-01-19
neverforget 写道 hobitton 写道 就像验证IPV4的IP地址一样,如果不熟悉IP地址的规则,用正则表达式傻乎乎的验证四段数字,会被人鄙视的。
为什么正则不能验证IPV4,很想知道哪里傻乎 (?<![\\d\\.])((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)(?![\\d\\.]) 见http://en.wikipedia.org/wiki/IPv4的“Address representations”这段。 也可以用windows的ping命令试试平常常见的4段十进制数以外的其他格式的IP地址。 |
|
返回顶楼 | |
发表时间:2011-01-20
http://commons.apache.org/configuration/
这个可以支持properties读写,注释保留。我应用过。 可以参考 |
|
返回顶楼 | |
发表时间:2011-01-21
hobitton 写道 neverforget 写道 hobitton 写道 就像验证IPV4的IP地址一样,如果不熟悉IP地址的规则,用正则表达式傻乎乎的验证四段数字,会被人鄙视的。
为什么正则不能验证IPV4,很想知道哪里傻乎 (?<![\\d\\.])((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)(?![\\d\\.]) 见http://en.wikipedia.org/wiki/IPv4的“Address representations”这段。 也可以用windows的ping命令试试平常常见的4段十进制数以外的其他格式的IP地址。 这么长一段正则表达式,除了自己写的能看懂,其他的人想看懂真够喝一壶的了 |
|
返回顶楼 | |