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

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  
马一个 待会看

相关推荐

    Java源码读写Properties文件.rar

    你可以通过查看这些源码来更好地理解如何实际操作Properties文件。 总结来说,Java中的Properties类是管理配置文件的关键工具,它提供了一套完整的API来读取、写入和操作键值对。掌握这部分知识对于任何Java开发者...

    基于jQuery.i18n.properties 实现资源国际化简单Demo 源码

    本文将深入探讨如何使用jQuery.i18n.properties库实现前端页面的资源国际化,并通过一个简单的Demo源码进行说明。 首先,我们要了解什么是jQuery.i18n.properties。这是一个jQuery插件,专门用于处理Web应用中的...

    提供j2me使用的优化过的Properties源码

    标题中提到的"提供j2me使用的优化过的Properties源码",意味着开发者为了解决J2ME环境中缺少Properties类的问题,编写了一个自定义的Properties实现。这个实现可能包含了一些优化策略,以适应J2ME的性能和资源限制。...

    properties-generator-源码.rar

    了解并掌握Properties Generator的源码,对于提升开发效率和理解Java I/O流、配置文件处理等方面的知识具有重要意义。 首先,让我们从源码结构入手。Properties Generator的核心功能主要分布在以下几个部分: 1. *...

    Spring MVC源码深度剖析开源架构源码2021.pdf

    对Spring MVC源码的深入剖析不仅有助于开发者更好地理解框架的工作机制,而且可以为开发定制化组件、性能优化及故障排查等提供坚实的知识基础。通过学习和实践,开发者可以更有效地利用Spring MVC框架来构建高性能的...

    网狐源码全套源码+详细架设教程

    5. 源码理解和修改:学习并理解框架的架构,根据需求修改源码。 6. 功能实现:利用框架的API和模块,开发各种网络应用功能。 7. 测试与调试:编写测试用例,确保代码质量和功能正确性。 8. 部署上线:将项目部署到...

    android中读取properties文件

    博文链接中提到的可能涉及对`Properties`类的源码分析,可以深入了解其实现原理,例如如何解析文件,如何处理转义字符,以及如何缓存加载的属性等。 7. 工具应用 开发过程中,可以使用IDE的内置功能或第三方插件...

    深入理解C#(c# in depth)第三版 源码

    源码的提供使得读者能够更加直观地理解书中所讲述的概念,通过实际操作加深对理论知识的掌握。 C#是一种面向对象的编程语言,由微软公司开发并用于.NET框架。深入理解C#,首先要了解它的基础语法,包括变量、数据...

    能保存Properties文件注释的Properties工具类

    在`CommentedProperties.java`源码中,可以看到类的设计思路。它可能包含以下关键部分: 1. `comments`字段:用于存储注释信息。 2. `keys`字段:存储键值对的键。 3. `values`字段:存储键值对的值。 4. 自定义的`...

    log4j.properties的简单运用

    理解并熟练运用这个配置文件,能帮助开发者更有效地管理和分析应用程序的运行状态,从而提高开发效率和问题排查能力。在实际项目中,可以根据需求调整日志级别、输出目的地和格式,以满足各种场景下的日志管理需求。

    java properties文件中文转化

    首先,我们需要理解Java Properties文件的编码问题。默认情况下,Java Properties类在读取和写入文件时使用ISO-8859-1编码,这是一种西欧字符集,不包含中文字符。当我们在properties文件中直接使用中文时,Java在...

    mule -esb 源码

    《深入解析Mule ESB源码》 Mule ESB(Enterprise Service Bus,企业服务总线)是一款开源的集成平台,旨在简化企业级应用之间的数据交互。本文将围绕Mule ESB的源码进行深入探讨,揭示其核心设计理念与工作原理。 ...

    noetic源码安装对应的源码

    sudo apt install curl software-properties-common curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.key | sudo apt-key add - ``` 然后更新包列表: ```bash sudo apt update ``` 现在...

    Android高级应用源码-android java 通用代码,关于用properties存储打印的Log.zip

    这个压缩包“Android高级应用源码-android java 通用代码,关于用properties存储打印的Log.zip”显然是一个关于如何在Android应用中使用Java的Properties类来管理日志打印的示例。下面我们将详细讨论这个知识点。 ...

    MyBatis源码分析.pdf

    MyBatis是一款流行的Java持久层框架,提供了强大的数据库访问能力,本文将对MyBatis的源码进行深入分析,从而帮助读者更好地理解MyBatis的工作机理。 1. MyBatis入门 MyBatis是一款基于Java的持久层框架,提供了...

    Properties类小结

    Properties类在Java编程中扮演着重要的角色,它是Java标准库中的一个核心类,主要用于处理配置文件或存储键值对的数据。...理解并熟练使用Properties类对于任何Java开发者来说都是必不可少的技能。

    精典源码之在线播放器源码.zip

    这样的源码通常包括了实现视频流媒体播放功能的各种组件和技术,可以帮助开发者理解和学习如何在移动设备上构建一个功能完备的在线播放器。 【描述】"源码参考,欢迎下载" 提示这个压缩包是作为学习资源提供的,...

    jar包源码关联

    然而,当遇到问题或者想要深入理解某个功能的实现时,查看源码是必不可少的。本篇文章将详细介绍如何将下载的JAR包源码与项目关联,以便在IDE中直接查看源码。 1. 引入JAR包: 首先,你需要将JAR包导入到你的项目...

    免费cms成品网站源码

    同时,“附详细使用说明”表示这套源码提供了一套完整的文档,可以帮助开发者理解和使用这个系统,减少了学习曲线,提高了开发效率。 【标签】"cms网站"进一步确认了这个资源是用于构建和管理网站的,尤其是那些...

    Android高级应用源码-android java 通用代码,关于用properties存储打印的Log.rar

    这个压缩包"Android高级应用源码-android java 通用代码,关于用properties存储打印的Log"提供了一种使用.properties文件来存储和打印Log的方法,这可以使得日志管理更加灵活和可配置。下面将详细解释这种技术的核心...

Global site tag (gtag.js) - Google Analytics