读取Java属性文件而不转义值

我的应用程序需要使用.properties文件进行配置。 在属性文件中,允许用户指定路径。

问题

属性文件需要转义值,例如

dir = c:\\mydir 

需要

我需要一些方法来接受未转义值的属性文件,以便用户可以指定:

 dir = c:\mydir 

为什么不简单地扩展属性类以包含双正斜杠的剥离。 这个的一个很好的特性是,通过程序的其余部分,您仍然可以使用原始的Properties类。

 public class PropertiesEx extends Properties { public void load(FileInputStream fis) throws IOException { Scanner in = new Scanner(fis); ByteArrayOutputStream out = new ByteArrayOutputStream(); while(in.hasNext()) { out.write(in.nextLine().replace("\\","\\\\").getBytes()); out.write("\n".getBytes()); } InputStream is = new ByteArrayInputStream(out.toByteArray()); super.load(is); } } 

使用新类很简单:

 PropertiesEx p = new PropertiesEx(); p.load(new FileInputStream("C:\\temp\\demo.properties")); p.list(System.out); 

剥离代码也可以改进,但一般原则是存在的。

两种选择:

  • 请改用XML属性格式
  • 编写自己的解析器以获得修改后的.properties格式,而无需转义

您可以在加载属性之前“预处理”文件,例如:

 public InputStream preprocessPropertiesFile(String myFile) throws IOException{ Scanner in = new Scanner(new FileReader(myFile)); ByteArrayOutputStream out = new ByteArrayOutputStream(); while(in.hasNext()) out.write(in.nextLine().replace("\\","\\\\").getBytes()); return new ByteArrayInputStream(out.toByteArray()); } 

你的代码看起来就像这样

 Properties properties = new Properties(); properties.load(preprocessPropertiesFile("path/myfile.properties")); 

这样做,您的.properties文件看起来就像您需要的那样,但您可以使用属性值。

*我知道应该有更好的方法来操作文件,但我希望这会有所帮助。

正确的方法是为用户提供属性文件编辑器(或他们喜欢的文本编辑器的插件),允许他们以纯文本forms输入文本,并以文件格式保存文件。

如果您不想这样做,那么您正在为属性文件的内容模型的相同(或子集)有效地定义新格式。

走完全路并实际指定你的格式,然后考虑一种方法

  • 将格式转换为规范格式,然后使用它来加载文件,或
  • 解析此格式并从中填充Properties对象。

这两种方法只有在您实际可以控制属性对象的创建时才能直接工作,否则您必须将转换后的格式存储在应用程序中。


那么,让我们看看我们如何定义它。 普通属性文件的内容模型很简单:

  • 字符串键到字符串值的映射,两者都允许任意Java字符串。

您想要避免的转义仅用于允许任意Java字符串,而不仅仅是这些字符串的子集。

通常足够的子集是:

  • 字符串键(不包含任何空格, := )到字符串值(不包含任何前导或尾随空格或换行符)的映射。

在你的例子中, dir = c:\mydir ,键是dir ,值是c:\mydir

如果我们希望我们的键和值包含任何Unicode字符(除了提到的禁用字符),我们应该使用UTF-8(或UTF-16)作为存储编码 – 因为我们无法转义存储之外的字符编码。 否则,US-ASCII或ISO-8859-1(作为普通属性文件)或Java支持的任何其他编码就足够了,但请确保将其包含在您的内容模型规范中(并确保以这种方式阅读) )。

由于我们限制了我们的内容模型,以便所有“危险”字符都不受影响,我们现在可以简单地定义文件格式:

  ::= (  )*  ::=  |  |   ::= * "#" < any text excluding line breaks >  ::= *  * "=" *  *  ::= *  ::= < any text excluding ':', '=' and whitespace >  ::= < any text starting and ending not with whitespace, not including line breaks >  ::= < any whitespace, but not a line break >  ::= < one of "\n", "\r", and "\r\n" > 

现在在键或值中出现的每个\都是真正的反斜杠,而不是任何逃脱其他东西的东西。 因此,为了将其转换为原始格式,我们只需要将其加倍,就像Grekz提出的那样,例如在过滤阅读器中:

 public DoubleBackslashFilter extends FilterReader { private boolean bufferedBackslash = false; public DoubleBackslashFilter(Reader org) { super(org); } public int read() { if(bufferedBackslash) { bufferedBackslash = false; return '\\'; } int c = super.read(); if(c == '\\') bufferedBackslash = true; return c; } public int read(char[] buf, int off, int len) { int read = 0; if(bufferedBackslash) { buf[off] = '\\'; read++; off++; len --; bufferedBackslash = false; } if(len > 1) { int step = super.read(buf, off, len/2); for(int i = 0; i < step; i++) { if(buf[off+i] == '\\') { // shift everything from here one one char to the right. System.arraycopy(buf, i, buf, i+1, step - i); // adjust parameters step++; i++; } } read += step; } return read; } } 

然后我们将此Reader传递给我们的Properties对象(或将内容保存到新文件)。

相反,我们可以自己简单地解析这种格式。

 public Properties parse(Reader in) { BufferedReader r = new BufferedReader(in); Properties prop = new Properties(); Pattern keyValPattern = Pattern.compile("\s*=\s*"); String line; while((line = r.readLine()) != null) { line = line.trim(); // remove leading and trailing space if(line.equals("") || line.startsWith("#")) { continue; // ignore empty and comment lines } String[] kv = line.split(keyValPattern, 2); // the pattern also grabs space around the separator. if(kv.length < 2) { // no key-value separator. TODO: Throw exception or simply ignore this line? continue; } prop.setProperty(kv[0], kv[1]); } r.close(); return prop; } 

再次,在此之后使用Properties.store() ,我们可以以原始格式导出它。

基于@Ian Harrigan,这是一个完整的解决方案,可以从ascii文本文件中获取Netbeans属性文件(以及其他转义属性文件):

 import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Reader; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Properties; /** * This class allows to handle Netbeans properties file. * It is based on the work of : http://stackoverflow.com/questions/6233532/reading-java-properties-file-without-escaping-values. * It overrides both load methods in order to load a netbeans property file, taking into account the \ that * were escaped by java properties original load methods. * @author stephane */ public class NetbeansProperties extends Properties { @Override public synchronized void load(Reader reader) throws IOException { BufferedReader bfr = new BufferedReader( reader ); ByteArrayOutputStream out = new ByteArrayOutputStream(); String readLine = null; while( (readLine = bfr.readLine()) != null ) { out.write(readLine.replace("\\","\\\\").getBytes()); out.write("\n".getBytes()); }//while InputStream is = new ByteArrayInputStream(out.toByteArray()); super.load(is); }//met @Override public void load(InputStream is) throws IOException { load( new InputStreamReader( is ) ); }//met @Override public void store(Writer writer, String comments) throws IOException { PrintWriter out = new PrintWriter( writer ); if( comments != null ) { out.print( '#' ); out.println( comments ); }//if List listOrderedKey = new ArrayList(); listOrderedKey.addAll( this.stringPropertyNames() ); Collections.sort(listOrderedKey ); for( String key : listOrderedKey ) { String newValue = this.getProperty(key); out.println( key+"="+newValue ); }//for }//met @Override public void store(OutputStream out, String comments) throws IOException { store( new OutputStreamWriter(out), comments ); }//met }//class 

您可以尝试使用guava的Splitter :拆分'='并从生成的Iterable构建一个映射。

此解决方案的缺点是它不支持注释。

@pdeva:还有一个解决方案

 //Reads entire file in a String //available in java1.5 Scanner scan = new Scanner(new File("C:/workspace/Test/src/myfile.properties")); scan.useDelimiter("\\Z"); String content = scan.next(); //Use apache StringEscapeUtils.escapeJava() method to escape java characters ByteArrayInputStream bi=new ByteArrayInputStream(StringEscapeUtils.escapeJava(content).getBytes()); //load properties file Properties properties = new Properties(); properties.load(bi); 

这不是您问题的准确答案,而是可能适合您需求的不同解决方案。 在Java中,您可以使用/作为路径分隔符,它可以在Windows,Linux和OSX上运行。 这对于相对路径特别有用。

在您的示例中,您可以使用:

 dir = c:/mydir