天天看点

java properties 冒号_java集合(四)Map集合之Properties详解

一、Properties类介绍

java.util.Properties继承自java.util.Hashtable,从jdk1.1版本开始,Properties的实现基本上就没有什么大的变动。从http://docs.oracle.com/javase/7/docs/api/的jdk7的官方api文档中我们可以看到对Properties类的介绍。Properties class是一个持久化的属性保存对象,可以将属性内容写出到stream中或者从stream中读取属性内容,在底层的Hashtable中,每一对属性的key和value都是按照string类型来保存的。 Properties可以将其他的Properties对象作为默认的值,Properties继承自Hashtable,所以Hashtable的所有方法Properties对象均可以访问。

Properties支持文本方式和xml方式的数据存储。在文本方式中,格式为key:value,其中分隔符可以是:冒号(:)、等号(=)、空格。其中空格可以作为key的结束,同时获取的值回将分割符号两端的空格去掉。

Properties只支持1对1模式的属性设置,而且不支持多层多级属性设置。

二、Properties类属性

protected Properties defaults:包含默认values的Properties对象,默认为null。我们在找不到对应key的情况下,就回递归的从这个默认列表中里面来找。

protectedProperties defaults;

Properties property

三、初始化方法

Properties提供两种方式来创建Properties对象,第一种是不指定默认values对象的创建方法,另外一种是指定默认values对象的创建方法。但是此时是没有加载属性值的,加载key/value属性必须通过专门的方法来加载。

publicProperties() {this(null);

}

publicProperties(Properties defaults) {this.defaults =defaults;

}

Properties Construction Method

四、常用方法

getProperty(String):根据指定的key获取对应的属性value值,如果在自身的存储集合中没有找到对应的key,那么就直接到默认的defaults属性指定的Properties中获取属性值。

publicString 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;

}

getProperty(String)

getProperty(String, String):当getProperty(String)方法返回值为null的时候,返回给定的默认值,而不是返回null。

publicString getProperty(String key, String defaultValue) {

String val=getProperty(key);return (val == null) ?defaultValue : val;

}

getProperty(String,String)

load(InputStream):从byte stream中加载key/value键值对,要求所有的key/value键值对是按行存储,同时是用ISO-8859-1编译的。

public synchronized voidload(InputStream inStream) throws IOException {

load0(newLineReader(inStream));

}

load(InputStream)

load(Reader):从字符流中加载key/value键值对,要求所有的键值对都是按照行来存储的。

/**

* Reads a property list (key and element pairs) from the input

* character stream in a simple line-oriented format.

*

* Properties are processed in terms of lines. There are two

* kinds of line, natural lines and logical lines.

* A natural line is defined as a line of

* characters that is terminated either by a set of line terminator

* characters (

\n

or

\r

or

\r\n

)

* or by the end of the stream. A natural line may be either a blank line,

* a comment line, or hold all or some of a key-element pair. A logical

* line holds all the data of a key-element pair, which may be spread

* out across several adjacent natural lines by escaping

* the line terminator sequence with a backslash character

*

\

. Note that a comment line cannot be extended

* in this manner; every natural line that is a comment must have

* its own comment indicator, as described below. Lines are read from

* input until the end of the stream is reached.

*

*

* A natural line that contains only white space characters is

* considered blank and is ignored. A comment line has an ASCII

*

'#'

or

'!'

as its first non-white

* space character; comment lines are also ignored and do not

* encode key-element information. In addition to line

* terminators, this format considers the characters space

* (

' '

,

'\u0020'

), tab

* (

'\t'

,

'\u0009'

), and form feed

* (

'\f'

,

'\u000C'

) to be white

* space.

*

*

* If a logical line is spread across several natural lines, the

* backslash escaping the line terminator sequence, the line

* terminator sequence, and any white space at the start of the

* following line have no affect on the key or element values.

* The remainder of the discussion of key and element parsing

* (when loading) will assume all the characters constituting

* the key and element appear on a single natural line after

* line continuation characters have been removed. Note that

* it is not sufficient to only examine the character

* preceding a line terminator sequence to decide if the line

* terminator is escaped; there must be an odd number of

* contiguous backslashes for the line terminator to be escaped.

* Since the input is processed from left to right, a

* non-zero even number of 2n contiguous backslashes

* before a line terminator (or elsewhere) encodes n

* backslashes after escape processing.

*

*

* The key contains all of the characters in the line starting

* with the first non-white space character and up to, but not

* including, the first unescaped

'='

,

*

':'

, or white space character other than a line

* terminator. All of these key termination characters may be

* included in the key by escaping them with a preceding backslash

* character; for example,

*

*

\:\=

*

* would be the two-character key

":="

. Line

* terminator characters can be included using

\r

and

*

\n

escape sequences. Any white space after the

* key is skipped; if the first non-white space character after

* the key is

'='

or

':'

, then it is

* ignored and any white space characters after it are also

* skipped. All remaining characters on the line become part of

* the associated element string; if there are no remaining

* characters, the element is the empty string

*

""

. Once the raw character sequences

* constituting the key and element are identified, escape

* processing is performed as described above.

*

*

* As an example, each of the following three lines specifies the key

*

"Truth"

and the associated element value

*

"Beauty"

:

*

*

* Truth = Beauty

* Truth:Beauty

* Truth :Beauty

*

* As another example, the following three lines specify a single

* property:

*

*

* fruits apple, banana, pear, \

* cantaloupe, watermelon, \

* kiwi, mango

*

* The key is

"fruits"

and the associated element is:

*

*

"apple, banana, pear, cantaloupe, watermelon, kiwi, mango"      

* Note that a space appears before each

\

so that a space

* will appear after each comma in the final result; the

\

,

* line terminator, and leading white space on the continuation line are

* merely discarded and are not replaced by one or more other

* characters.

*

* As a third example, the line:

*

*

cheeses      

*

* specifies that the key is

"cheeses"

and the associated

* element is the empty string

""

.

*

*

*

* Characters in keys and elements can be represented in escape

* sequences similar to those used for character and string literals

* (see sections 3.3 and 3.10.6 of

* The Java™ Language Specification).

*

* The differences from the character escape sequences and Unicode

* escapes used for characters and strings are:

*

*

*

Octal escapes are not recognized.

*

*

The character sequence

\b

does not

* represent a backspace character.

*

*

The method does not treat a backslash character,

*

\

, before a non-valid escape character as an

* error; the backslash is silently dropped. For example, in a

* Java string the sequence

"\z"

would cause a

* compile time error. In contrast, this method silently drops

* the backslash. Therefore, this method treats the two character

* sequence

"\b"

as equivalent to the single

* character

'b'

.

*

*

Escapes are not necessary for single and double quotes;

* however, by the rule above, single and double quote characters

* preceded by a backslash still yield single and double quote

* characters, respectively.

*

*

Only a single 'u' character is allowed in a Uniocde escape

* sequence.

*

*

*

* The specified stream remains open after this method returns.

*

* @param reader the input character stream.

* @throws IOException if an error occurred when reading from the

* input stream.

* @throws IllegalArgumentException if a malformed Unicode escape

* appears in the input.

* @since 1.6*/

public synchronized voidload(Reader reader) throws IOException {

load0(newLineReader(reader));

}

load(Reader)

loadFromXML(InputStream):从xml文件中加载property,底层使用XMLUtils.load(Properties,InputStream)方法来加载。

/**

* Loads all of the properties represented by the XML document on the

* specified input stream into this properties table.

*

*

The XML document must have the following DOCTYPE declaration:

*

* <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

*

* Furthermore, the document must satisfy the properties DTD described

* above.

*

*

The specified stream is closed after this method returns.

*

* @param in the input stream from which to read the XML document.

* @throws IOException if reading from the specified input stream

* results in an IOException.

* @throws InvalidPropertiesFormatException Data on input stream does not

* constitute a valid XML document with the mandated document type.

* @throws NullPointerException if

in

is null.

* @see #storeToXML(OutputStream, String, String)

* @since 1.5*/

public synchronized void loadFromXML(InputStream in)

throws IOException, InvalidPropertiesFormatException

{if (in == null)throw newNullPointerException();

XMLUtils.load(this, in);in.close();

}

loadFromXML(InputStream)

store(OutputStream/Writer,comments)将所有的property(保存defaults的)都写出到流中,同时如果给定comments的话,那么要加一个注释。

public voidstore(Writer writer, String comments)

throws IOException

{

store0((writer instanceof BufferedWriter)?(BufferedWriter)writer

:newBufferedWriter(writer),

comments,false);

}

public void store(OutputStream out, String comments)

throws IOException

{

store0(new BufferedWriter(new OutputStreamWriter(out, "8859_1")),

comments,true);

}

store(...)

storeToXML(OutputSteam, comment, encoding):写出到xml文件中。

/**

* Emits an XML document representing all of the properties contained

* in this table, using the specified encoding.

*

*

The XML document will have the following DOCTYPE declaration:

*

* <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

*

*

*

If the specified comment is

null

then no comment

* will be stored in the document.

*

*

The specified stream remains open after this method returns.

*

* @param os the output stream on which to emit the XML document.

* @param comment a description of the property list, or

null

* if no comment is desired.

* @param encoding the name of a supported

*

* character encoding

*

* @throws IOException if writing to the specified output stream

* results in an IOException.

* @throws NullPointerException if

os

is

null

,

* or if

encoding

is

null

.

* @throws ClassCastException if this

Properties

object

* contains any keys or values that are not

*

Strings

.

* @see #loadFromXML(InputStream)

* @since 1.5*/

public voidstoreToXML(OutputStream os, String comment, String encoding)

throws IOException

{if (os == null)throw newNullPointerException();

XMLUtils.save(this, os, comment, encoding);

}

storeToXML(...)

四、源码分析

主要针对加载属性方法(load/loadFromXML)和写出属性到磁盘文件方法来进行分析(store/storeToXML)。

1、load(Reader)和load(InputStream)

这两个方法是指定从文本文件中加载key/value属性值,底层都是将流封装成为LineReader对象,然后通过load0方法来加载属性键值对的,加载完属性后流对象是不会关闭的。这两个方法对应的properties文件格式如下:

# this iscomment

key1:value1

key2=value2

key3 : vlaue3

key4 : value4

# the valueis 'value4', because the Properties only trim the space of the split charset before and after.

# key5=value5

#this iserror, the key not start with the space.

key6 value7

Properties Text File

LineReader源码分析:

classLineReader {

publicLineReader(InputStream inStream) {this.inStream =inStream;

inByteBuf= new byte[8192];

}

publicLineReader(Reader reader) {this.reader =reader;

inCharBuf= new char[8192];

}//字节流缓冲区, 大小为8192个字节

byte[] inByteBuf;//字符流缓冲区,大小为8192个字符

char[] inCharBuf;//当前行信息的缓冲区,大小为1024个字符

char[] lineBuf = new char[1024];//读取一行数据时候的实际读取大小

int inLimit = 0;//读取的时候指向当前字符位置

int inOff = 0;//字节流对象

InputStream inStream;//字符流对象

Reader reader;

intreadLine() throws IOException {//总的字符长度

int len = 0;//当前字符

char c = 0;

boolean skipWhiteSpace= true;

boolean isCommentLine= false;

boolean isNewLine= true;

boolean appendedLineBegin= false;

boolean precedingBackslash= false;

boolean skipLF= false;while (true) {if (inOff >=inLimit) {//读取一行数据,并返回这一行的实际读取大小

inLimit = (inStream == null) ?reader.read(inCharBuf) : inStream.read(inByteBuf);

inOff= 0;//如果没有读取到数据,那么就直接结束读取操作

if (inLimit <= 0) {//如果当前长度为0或者是改行是注释,那么就返回-1。否则返回len的值。

if (len == 0 ||isCommentLine) {return -1;

}returnlen;

}

}//判断是根据字符流还是字节流读取当前字符

if (inStream != null) {//The line below is equivalent to calling a ISO8859-1 decoder.//字节流是根据ISO8859-1进行编码的,所以在这里进行解码操作。

c = (char) (0xff & inByteBuf[inOff++]);

}else{

c= inCharBuf[inOff++];

}//如果前一个字符是换行符号,那么判断当前字符是否也是换行符号

if(skipLF) {

skipLF= false;if (c == '\n') {continue;

}

}//如果前一个字符是空格,那么判断当前字符是不是空格类字符

if(skipWhiteSpace) {if (c == ' ' || c == '\t' || c == '\f') {continue;

}if (!appendedLineBegin && (c == '\r' || c == '\n')) {continue;

}

skipWhiteSpace= false;

appendedLineBegin= false;

}//如果当前新的一行,那么进入该if判断中

if(isNewLine) {

isNewLine= false;//如果当前字符是#或者是!,那么表示该行是一个注释行

if (c == '#' || c == '!') {

isCommentLine= true;continue;

}

}//根据当前字符是不是换行符号进行判断操作

if (c != '\n' && c != '\r') {//当前字符不是换行符号

lineBuf[len++] = c;//将当前字符写入到行信息缓冲区中,并将len自增加1.//如果len的长度大于行信息缓冲区的大小,那么对lineBuf进行扩容,扩容大小为原来的两倍,最大为Integer.MAX_VALUE

if (len ==lineBuf.length) {int newLength = lineBuf.length * 2;if (newLength < 0) {

newLength=Integer.MAX_VALUE;

}char[] buf = new char[newLength];

System.arraycopy(lineBuf,0, buf, 0, lineBuf.length);

lineBuf=buf;

}//是否是转义字符//flip the preceding backslash flag

if (c == '\\') {

precedingBackslash= !precedingBackslash;

}else{

precedingBackslash= false;

}

}else{//reached EOL

if (isCommentLine || len == 0) {//如果这一行是注释行,或者是当前长度为0,那么进行clean操作。

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) {returnlen;

}

}//查看是否是转义字符

if(precedingBackslash) {//如果是,那么表示是另起一行,进行属性的定义,len要自减少1.

len -= 1;//skip the leading whitespace characters in following line

skipWhiteSpace = true;

appendedLineBegin= true;

precedingBackslash= false;if (c == '\r') {

skipLF= true;

}

}else{returnlen;

}

}

}

}

}

根据这个源码,我们可以看出一些特征:readLine这个方法每次读取一行数据;如果我们想在多行写数据,那么可以使用'\'来进行转义,在该转义符号后面换行,是被允许的。

load0方法源码:

private voidload0(LineReader lr) throws IOException {char[] convtBuf = new char[1024];//读取的字符总数

intlimit;//当前key所在位置

intkeyLen;//value的起始位置

intvalueStart;//当前字符

charc;//

boolean hasSep;//是否是转义字符

boolean precedingBackslash;while ((limit = lr.readLine()) >= 0) {

c= 0;//key的长度

keyLen = 0;//value的起始位置默认为limit

valueStart =limit;//

hasSep = false;

precedingBackslash= false;//如果key的长度小于总的字符长度,那么就进入循环

while (keyLen

c =lr.lineBuf[keyLen];//如果当前字符是=或者是:,而且前一个字符不是转义字符,那么就表示key的描述已经结束

if ((c == '=' || c == ':') && !precedingBackslash) {//指定value的起始位置为当前keyLen的下一个位置

valueStart = keyLen + 1;//并且指定,去除空格

hasSep = true;break;

}else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) {//如果当前字符是空格类字符,而且前一个字符不是转义字符,那么表示key的描述已经结束//指定value的起始位置为当前位置的下一个位置

valueStart = keyLen + 1;break;

}//如果当前字符为'\',那么跟新是否是转义号。

if (c == '\\') {

precedingBackslash= !precedingBackslash;

}else{

precedingBackslash= false;

}

keyLen++;

}//如果value的起始位置小于总的字符长度,那么就进入该循环

while (valueStart

c =lr.lineBuf[valueStart];//判断当前字符是否是空格类字符,达到去空格的效果

if (c != ' ' && c != '\t' && c != '\f') {//当前字符不是空格类字符,而且当前字符为=或者是:,并在此之前没有出现过=或者:字符。//那么value的起始位置继续往后移动。

if (!hasSep && (c == '=' || c == ':')) {

hasSep= true;

}else{//当前字符不是=或者:,或者在此之前出现过=或者:字符。那么结束循环。

break;

}

}

valueStart++;

}//读取key

String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);//读取value

String value = loadConvert(lr.lineBuf, valueStart, limit -valueStart, convtBuf);//包括key/value

put(key, value);

}

}

我们可以看到,在这个过程中,会将分割符号两边的空格去掉,并且分割符号可以是=,:,空格等。而且=和:的级别比空格分隔符高,即当这两个都存在的情况下,是按照=/:分割的。可以看到在最后会调用一个loadConvert方法,该方法主要是做key/value的读取,并将十六进制的字符进行转换。

2、loadFromXML方法

该方法主要是提供一个从XML文件中读取key/value键值对的方法。底层是调用的XMLUtil的方法,加载完对象属性后,流会被显示的关闭。xml格式如下所示:

comments

value7

value7

value4

vlaue3

value2

value1

Properties XML File

底层调用的是XMLUtil.load方法,在该方法中是使用DOM方式来访问xml文件的,在这里不做详细的介绍。

3、store(InputStream/Reader,String)方法

该方法主要是将属性值写出到文本文件中,并写出一个comment的注释。底层调用的是store0方法。针对store(InputStream,String)方法,我们可以看到在调用store0方法的时候,进行字节流封装成字符流,并且指定字符集为8859-1。源码如下:

private voidstore0(BufferedWriter bw, String comments, boolean escUnicode) throws IOException {if (comments != null) {//写出注释, 如果是中文注释,那么转化成为8859-1的字符

writeComments(bw, comments);

}//写出时间注释

bw.write("#" + newDate().toString());//新起一行

bw.newLine();//进行线程间同步的并发控制

synchronized (this) {for (Enumeration e =keys(); e.hasMoreElements();) {

String key=(String) e.nextElement();

String val= (String) get(key);//针对空格进行转义,并根据是否需要进行8859-1编码

key = saveConvert(key, true, escUnicode);

//value不对空格进行转义

val = saveConvert(val, false, escUnicode);//写出key/value键值对

bw.write(key + "=" +val);

bw.newLine();

}

}

bw.flush();

}

4、storeToXML方法

将属性写出到xml文件中,底层调用的是XMLUtil.store方法。不做详细的介绍。

五、实例

直接代码:

package com.gerry.bd.properties.jdk;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.util.Properties;

import java.util.Set;

public classPropertiesApp {public static voidmain(String[] args) {

InputStream input= null;//第一种,使用ClassLoad的方法获取InputStram对象。

input = PropertiesApp.class.getClassLoader().getResourceAsStream("propertiesApp.properties");//第二种,直接使用Class的方法来获取InputStream对象。必须加'/'表示在classpath路径下,如果不加的话,那么获取的是PropertiesApp这个类所在package下的文件。

input = PropertiesApp.class.getResourceAsStream("/propertiesApp.properties");

OutputStream os= null;try{

os= new FileOutputStream("storePropertiesApp.xml");

}catch(FileNotFoundException e1) {

}//第一步:创建Properties对象

Properties prop = newProperties();try{//第二步:加载属性, 不会自动关闭input输入流。

prop.load(input);//第三步:获取属性

String value1 = prop.getProperty("key1");

String value5= prop.getProperty("key5");

String value7= prop.getProperty("key7", "defaultvalue");

System.out.println("[key1:" + value1 + "],[key5:" + value5 + "],[key7:" + value7 + "]");

Set keys =prop.stringPropertyNames();

System.out.println("全部的key/value属性:");for(String key : keys) {

System.out.println("[" + key + "][" + prop.getProperty(key) + "]");

}//第四步:设置属性

prop.setProperty("key7", "value7");//第五步:保存成文件

prop.storeToXML(os, "comments");

}catch(IOException e) {

e.printStackTrace();

}finally{if(input != null) {try{

input.close();

}catch(IOException e) {//ignore

}

}if (os != null) {try{

os.close();

}catch(IOException e) {//ignore

}

}

}

}

}

PropertiesApp

结果console的输出为:

[key1:value1],[key5:null],[key7:defaultvalue]

全部的key/value属性:

[key6][value7]

[key4][value4 ]

[key3][vlaue3]

[key2][value2]

[key1][value1]

result