Properties源码理解
程序员文章站
2022-04-30 15:19:18
...
Properties用来读配置文件的对象,用的很多。
使用方法
// 新建一个对象 Properties pro = new Properties(); // 加载字节流 pro.load(new FileInputStream("abc.txt")); // 取值 System.out.println(pro.getProperty("key")); // 修改 pro.setProperty("key", "value");
0.几个问题
1.a=b=c 会被拆分成什么样? 答:key="a",value="b=c" 2.字符中有 = 该怎么办? 答:办法一:\= 办法二:\u003D 3.a<空格><空格>d = 2<空格>44 可以作为一个正常的配置么? 答:不能,空格会作为key的结束 4.a=2<空格>34<空格>5 最后的值value为多少? 答: "2 34 5" 5.在配置文件中最后的空格会被去掉吗? 答:不会被去掉,程序中只会去掉前面和等号左右的空格。 6.想在配置文件里换行改怎么办 答:在回车前输入一个\ 7.pro.load(new FileInputStream("abc.txt")); 这样打开abc.txt的文件流关闭没有? 答:流未关闭,需要手动关闭,不建议这样写。但是虚拟机在回收这个对象的时候会关闭流。
1.构造方法
类继承了hashTable类
public class Properties extends Hashtable<Object,Object>
本身是一个Map ,这个对象主要用来储存key-value的键值对,所以可以使用另一个Properties来初始化自己。
protected Properties defaults; public Properties() { this(null); } public Properties(Properties defaults) { this.defaults = defaults; }
2.使用的第一步,load()。
先简单的说一下load的执行过程:
1.读一行。
2.找到key 和 value 。
3.存入map中<String,String>
//下面是Properties类中方法 public synchronized void load(InputStream inStream) throws IOException { //将输出流转成LineReader,LineReader是Properties的内部类,功能是可以一行一行的读数据。 load0(new LineReader(inStream)); } //私有 private void load0 (LineReader lr) throws IOException { //由于key和value中会有转义字符串,所以用这个数组来存储key或者value的转义后的数据。 char[] convtBuf = new char[1024]; int limit;//一行字符数(除一行前面空格,到末尾的回车符) int keyLen;//key值长度 int valueStart;//value起始 char c;//在循环遍历时暂存一个字符,临时使用,命名也很不规范。 boolean hasSep;//是否有分割符 = 或者: boolean precedingBackslash; //当前读取是否为转义字符 /*循环读取每一行,每一行分析出key和value。limit为每一行字符数长度(除前面空格),-1表示流读完了 */ while ((limit = lr.readLine()) >= 0) { c = 0; keyLen = 0; valueStart = limit; hasSep = false; //下面的system是原作者写的,不认真。 //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">"); //字面上是反斜杠的意思,表示碰到了转义的反斜杠"\"。 precedingBackslash = false; /*这个循环主要找出key结束的地方,也简单的找了一个value大概开始部分。*/ while (keyLen < limit) { //这个lineBuf是LineReader里的一个字符数组,存了一行字符。 c = lr.lineBuf[keyLen]; //找到分隔符 if ((c == '=' || c == ':') && !precedingBackslash) { //value的起始点 valueStart = keyLen + 1; //找到key和value的分隔符 hasSep = true; //找到key结束的位置,跳出循环 break; /*同样空格\t \f 也是key结束的地方*/ } else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { valueStart = keyLen + 1; break; } //如果是反斜杠,则跳过下一个字符 if (c == '\\') { //连续两个反斜杠的话又变为false了。 precedingBackslash = !precedingBackslash; } else { //未碰到变为false。 precedingBackslash = false; } //这个就是key的长度。 keyLen++; } /*计算value的开始点*/ while (valueStart < limit) { c = lr.lineBuf[valueStart]; /*这个if是为了去掉value前的空格字符*/ if (c != ' ' && c != '\t' && c != '\f') { /*这个if是为了在key值中因为空格跳出,然后空格后接着是“="或":"的情况下*/ if (!hasSep && (c == '=' || c == ':')) { //有了分割key和value的标识 hasSep = true; } else { //结束value开始的检查 break; } } valueStart++; } //取key和value。 String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); //这个类继承了HashTable put(key, value); } }
紧接着,我们看一下怎么取出key和value,并且将其内部的转义符都去掉。
/* 加载转化, * in 传入的字符数组 * off 要取值开始的地方 * len 要取的长度 * convtbuf 输出的字符数组 */ private String loadConvert (char[] in, int off, int len, char[] convtBuf) { //如果数组的长度小于要取长度 if (convtBuf.length < len) { int newLen = len * 2; //如果传入的len为负的,性能就非常差了,少个判断! if (newLen < 0) { newLen = Integer.MAX_VALUE; } convtBuf = new char[newLen]; } char aChar; char[] out = convtBuf; //换个名字 int outLen = 0; //输出的长度 int end = off + len;//要取字符结束的地方 //循环从要取字符开始到结束。 while (off < end) { aChar = in[off++]; //如果是转义字符 if (aChar == '\\') { aChar = in[off++]; //如果是unicode if(aChar == 'u') { //读4位,4位表示unicode的编码集. int value=0; //下面的步骤可以理解成Integer.paseInt("7EA2",16) for (int i=0; i<4; i++) { aChar = in[off++]; switch (aChar) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': value = (value << 4) + aChar - '0'; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': value = (value << 4) + 10 + aChar - 'a'; break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': value = (value << 4) + 10 + aChar - 'A'; break; default: //抛出的这个异常不需要捕获。 throw new IllegalArgumentException( "Malformed \\uxxxx encoding."); } } //写入输出的数组 out[outLen++] = (char)value; } else { //这样就其它转义字符处理了 if (aChar == 't') aChar = '\t'; else if (aChar == 'r') aChar = '\r'; else if (aChar == 'n') aChar = '\n'; else if (aChar == 'f') aChar = '\f'; //这一步非常关键,它直接忽略转义符号\ ,举例:\= 会存为 = , \\ 会存为\ , \?会存为 ?,\<空格> 会存 为 <空格> out[outLen++] = aChar; } } else { //无转义 out[outLen++] = (char)aChar; } } return new String (out, 0, outLen); }
它是怎么读取一行数据的?下面需要看一下Properties的内部类LineReader
3.Properties的内部类LineReader
3.1首先是无任何继承
class LineReader {}
3.2构造方法
字符流和字节流通吃。
public LineReader(InputStream inStream) { this.inStream = inStream; inByteBuf = new byte[8192]; } public LineReader(Reader reader) { this.reader = reader; inCharBuf = new char[8192]; }
3.3 readLine方法
简单的看一下吧,这个不是重点
文字介绍:读取每一个字符,空白的跳过,碰到一行第一个字符检查是否为#或者!,继续循环直到碰到\r 或者\n ,然后结束进入一个新一行的循环。末尾的空格不去掉。
int readLine() throws IOException { int len = 0; //数组长度 //另外一些变量 ... while (true) { //从字节流中读取 8192个字节,或者从字符流中读取8192个字符 if (inOff >= inLimit) { inLimit = (inStream==null)?reader.read(inCharBuf) :inStream.read(inByteBuf); //如果流读到末尾 if (inLimit <= 0) { //流的长度是0,或者是注释,直接返回-1,结束。 if (len == 0 || isCommentLine) { return -1; } return len; } } //取出一个字符 ... //碰到\\r的情况,意思是\\r\n 或者 \\r \\n都不会进行换行,同样\\r \\r\n \\n 也不加入字符数组中。 if (skipLF) { skipLF = false; if (c == '\n') { continue; } } //跳过空白 if (skipWhiteSpace) { if (c == ' ' || c == '\t' || c == '\f') { continue; } //如果没有开始,并且字符时\r \n 的,同样跳过。 if (!appendedLineBegin && (c == '\r' || c == '\n')) { continue; } //如果不是上述情况,则代表已经不是空格字符了。 skipWhiteSpace = false; appendedLineBegin = false; } //如果是新的一行 if (isNewLine) { isNewLine = false; //如果是注释 if (c == '#' || c == '!') { isCommentLine = true; continue; } } //检查字符是不是换行 if (c != '\n' && c != '\r') {//不是换行 lineBuf[len++] = c; //下面是检查是否越界了 ... //同样处理转义字符 ... } else {// 这里是找到了换行的字符了 //注释或者空行 if (isCommentLine || len == 0) { isCommentLine = false; isNewLine = true; skipWhiteSpace = true; len = 0; continue; } //如果当前字符超过了读取到的字符数。 if (inOff >= inLimit) { inLimit = (inStream==null) ?reader.read(inCharBuf) :inStream.read(inByteBuf); inOff = 0; if (inLimit <= 0) { return len; } } //下面还有回车前碰到转义符的情况 ... } } }
4.取值和修改
在load里面使用put存储了数据,这里取值很简单。
public String getProperty(String key) { Object oval = super.get(key); String sval = (oval instanceof String) ? (String)oval : null; return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval; } public String getProperty(String key) { Object oval = super.get(key); String sval = (oval instanceof String) ? (String)oval : null; return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval; }
5.方法中也包含着将数据存成文本的功能。
在存储xml的时候,用到了 java.util.XMLUtils 来解析xml。
storeToXML() loadFromXML()
6.读取时候去除绝对路径
props.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("config.properties"));
7.结束
上一篇: Firebug Lite 1.2 发布