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.结束