【问题标题】:Reading Java Properties file without escaping values读取 Java 属性文件而不转义值
【发布时间】:2011-09-08 04:17:01
【问题描述】:

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

问题

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

dir = c:\\mydir

需要

我需要某种方式来接受不转义值的属性文件,以便用户可以指定:

dir = c:\mydir

【问题讨论】:

    标签: java properties configuration-files


    【解决方案1】:

    为什么不简单地扩展属性类来合并双斜杠的剥离。这样做的一个好处是,在您的程序的其余部分中,您仍然可以使用原始的 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);
    

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

    【讨论】:

    • 感谢您的提示。我创建了一个静态实用程序类来加载和保存属性文件,但你的方式更干净。顺便说一句,这就是加载一些 netbeans 属性文件(例如 project.properties)所需要的,尤其是在使用移动包时。
    【解决方案2】:

    两种选择:

    • 改用XML properties 格式
    • 为修改后的 .properties 格式编写您自己的解析器,无需转义

    【讨论】:

    • 不想使用 xml 属性。
    【解决方案3】:

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

    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 文件看起来就像您需要的那样,但您将拥有可供使用的属性值。

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

    【讨论】:

      【解决方案4】:

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

      如果您不希望这样,您实际上是在为与属性文件所具有的相同(或子集)内容模型定义新格式。

      一路走来,真正指定您的格式,然后考虑一种方法

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

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


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

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

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

      通常足够的子集是:

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

      在您的示例dir = c:\mydir 中,键是dir,值是c:\mydir

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

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

      <simplepropertyfile> ::= (<line> <line break> )*
      <line>               ::= <comment> | <empty> | <key-value>
      <comment>            ::= <space>* "#" < any text excluding line breaks >
      <key-value>          ::= <space>* <key> <space>* "=" <space>* <value> <space>*
      <empty>              ::= <space>*
      <key>                ::= < any text excluding ':', '=' and whitespace >
      <value>              ::= < any text starting and ending not with whitespace,
                                 not including line breaks >
      <space>              ::= < any whitespace, but not a line break >
      <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(),我们可以将其导出为原始格式。

      【讨论】:

        【解决方案5】:

        基于@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<String> listOrderedKey = new ArrayList<String>();
                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
        

        【讨论】:

          【解决方案6】:

          您可以尝试使用 guava 的 Splitter: split on '=' 并从生成的 Iterable 构建地图。

          这个方案的缺点是不支持cmets。

          【讨论】:

          • 另外:严格来说,具有未转义值的 .properties 文件不是为 .properties 文件指定的格式。该格式比许多人想象的要复杂(例如,它被指定为始终采用 ISO-8859-1 编码并支持\u unicode 转义)。
          【解决方案7】:

          @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);
          

          【讨论】:

          • 如果您使用new Scanner(...).getBytes(),请确保它们在此处使用正确的字符集(ISO-8859-1,因为这是 properties.load 使用的)。但最好让数据保持字符形式,而不是将它们转换为字节并返回,即使用 StringReader 而不是 ByteArrayInputStream。
          【解决方案8】:

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

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

          dir = c:/mydir
          

          【讨论】:

          • 我们的用户不会知道临时使用“/”。如果我们必须告诉他们使用'/',我们也可以告诉他们使用\\
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-09-21
          • 2012-01-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多