`
wdhdmx
  • 浏览: 300121 次
  • 性别: Icon_minigender_1
  • 来自: 山西
博客专栏
D4bbb5f7-9aa4-3e66-8194-f61b3f0241c2
天天编程
浏览量:21490
社区版块
存档分类
最新评论

Properties源码理解

阅读更多

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

 

2
4
分享到:
评论
2 楼 output 2012-07-10  
josico 写道
马一个 待会看

估计不会再看
我码了很多,,,都没再看了
1 楼 josico 2012-07-05  
马一个 待会看

相关推荐

Global site tag (gtag.js) - Google Analytics