【问题标题】:Pulling values from a Java Properties file in order?按顺序从 Java 属性文件中提取值?
【发布时间】:2010-11-21 16:39:21
【问题描述】:

我有一个属性文件,其中值的顺序很重要。我希望能够遍历属性文件并根据原始文件的顺序输出值。

但是,由于属性文件是由不维护插入顺序的 Map 支持的,如果我错了,请纠正我,迭代器以错误的顺序返回值

这是我正在使用的代码

Enumeration names = propfile.propertyNames();
while (names.hasMoreElements()) {
    String name = (String) names.nextElement();
    //do stuff
}

除了编写我自己的自定义文件解析器之外,有没有办法恢复属性?

【问题讨论】:

    标签: java configuration properties maps


    【解决方案1】:

    不 - 地图本质上是“无序的”。

    您可以可能创建自己的 Properties 子类,它会覆盖 setProperty 和可能的 put,但它可能会变得非常特定于实现...... Properties 是一个主要的封装不良的例子。当我上次写一个扩展版本时(大约 10 年前!)它最终变得很糟糕,并且对 Properties 的实现细节绝对敏感。

    【讨论】:

    • 我害怕那个。我现在正在查看属性代码,我明白你的意思。支持的实现应该是一个可设置的委托。你能推荐任何替代品吗? Apache Commons 配置是否可以帮助我?
    • 快速更正一下,Java 确实有一个 Map 的实现 LinkedHashMap,它确实保持插入顺序。
    • @nemo:是的,但这是专门为此设计的地图。地图一般没有顺序。我相信 Spring 有自己的属性文件阅读器,您可能会觉得它很有用。
    • 扩展属性,覆盖 put() 并将键存储在内部列表中。使用所述列表按顺序迭代属性。
    • [DataMember(Name = "attribute ID", Order = 0)] private int _attributeID;我们不能在 Java 中有这样的东西吗
    【解决方案2】:

    如果您可以更改属性名称,您可以在它们前面加上数字或其他可排序的前缀,然后对 Properties KeySet 进行排序。

    【讨论】:

    • 是的,我想到了。这可能是最简单的解决方法。
    • 可能不会给出你想要的,因为字符串可排序与混合字符串的整数可排序不匹配。如果您按字符串的自然排序值对字符串进行排序,则 11-SomePropName 将在 2-OtherPropName 之前排序。
    • 这实际上是我最终做的。我只处理四个值,而公共配置需要太多的依赖项,这会使我的构建变得复杂。
    • 但是此方法按字母顺序对条目进行排序并且不保证插入顺序(原始文件的顺序)
    【解决方案3】:

    Apache Commons Configuration 可能会为您解决问题。我自己没有对此进行测试,但我检查了它们的来源,看起来属性键由 AbstractFileConfiguration 类中的 LinkedList 支持:

    public Iterator getKeys()
    {
        reload();
        List keyList = new LinkedList();
        enterNoReload();
        try
        {
            for (Iterator it = super.getKeys(); it.hasNext();)
            {
                keyList.add(it.next());
            }
    
            return keyList.iterator();
        }
        finally
        {
            exitNoReload();
        }
    }
    

    【讨论】:

    • 这看起来很有趣,但是使用一个公地需要加载其他几个。我最终选择了一个快速而肮脏的解决方案。
    • 大部分依赖是可选的。对于简单的 PropertiesConfiguration,您只需要 Commons Lang 和 Commons Collections。
    【解决方案4】:

    扩展java.util.Properties,同时覆盖put()keys()

    import java.util.Collections;
    import java.util.Enumeration;
    import java.util.HashSet;
    import java.util.LinkedHashSet;
    import java.util.Properties;
    import java.util.HashMap;
    
    public class LinkedProperties extends Properties {
        private final HashSet<Object> keys = new LinkedHashSet<Object>();
    
        public LinkedProperties() {
        }
    
        public Iterable<Object> orderedKeys() {
            return Collections.list(keys());
        }
    
        public Enumeration<Object> keys() {
            return Collections.<Object>enumeration(keys);
        }
    
        public Object put(Object key, Object value) {
            keys.add(key);
            return super.put(key, value);
        }
    }
    

    【讨论】:

    • 将您的类创建为属性的包装器比扩展它更安全。只有重写这些方法才能假设底层类的工作方式(假设 putAll() 使用 put()),并且您可能会遇到键设置不正确的情况。
    • 你真的应该重写 remove() 和 clear() - 否则你会在 save() 上得到 NullPointerExceptions!此外,除非用于键的集合是线程安全的,否则您应该在父方法中添加同步。
    • 类型可以在return Collections.&lt;Object&gt;enumeration(keys);中推断出来,这样就足够了:return Collections.enumeration(keys);
    • 解释代码:诀窍是使用Java的Properties类,重写它的put方法并将密钥放入保存插入顺序的数据结构中。这是有效的,因为Properties 将键/值按照从文件中读取它们的顺序放置 - 顶部->底部。
    • -1 - 您假设 put 方法是根据源文件中属性的顺序调用的。我认为你不能假设这是有保证的。如果文件是从下到上读取的,您将以相反的顺序获得属性。
    【解决方案5】:

    如果要将属性导出为 XML,还必须覆盖 keySet():

    public Set<Object> keySet() { return keys; }

    【讨论】:

      【解决方案6】:

      Dominique Laurent 的上述解决方案非常适合我。我还添加了以下方法覆盖:

      public Set<String> stringPropertyNames() {
          Set<String> set = new LinkedHashSet<String>();
      
          for (Object key : this.keys) {
              set.add((String)key);
          }
      
          return set;
      }
      

      可能不是最有效的,但它在我的 servlet 生命周期中只执行一次。

      谢谢多米尼克!

      【讨论】:

        【解决方案7】:

        为了完整性...

        public class LinkedProperties extends Properties {
        
            private final LinkedHashSet<Object> keys = new LinkedHashSet<Object>();
        
            @Override
            public Enumeration<?> propertyNames() {
                return Collections.enumeration(keys);
            }
        
            @Override
            public synchronized Enumeration<Object> elements() {
                return Collections.enumeration(keys);
            }
        
            public Enumeration<Object> keys() {
                return Collections.enumeration(keys);
            }
        
            public Object put(Object key, Object value) {
                keys.add(key);
                return super.put(key, value);
            }
        
            @Override
            public synchronized Object remove(Object key) {
                keys.remove(key);
                return super.remove(key);
            }
        
            @Override
            public synchronized void clear() {
                keys.clear();
                super.clear();
            }
        }
        

        我不认为返回集合的方法应该被覆盖,因为定义的集合不维护插入顺序

        【讨论】:

          【解决方案8】:

          另一种方法是使用 LinkedHashMap 编写自己的属性文件,这是我使用的:

          import java.io.File;
          import java.io.IOException;
          import java.net.URL;
          import java.util.Arrays;
          import java.util.LinkedHashMap;
          import java.util.List;
          import java.util.Map;
          
          import org.apache.commons.io.FileUtils;
          import org.apache.commons.io.LineIterator;
          
          public class OrderedProperties {
          
              private static Map<String, String> properties = new LinkedHashMap<String, String>();
          
              private static OrderedProperties instance = null;
          
              private OrderedProperties() {
          
              }
          
              //The propertyFileName is read from the classpath and should be of format : key=value
              public static synchronized OrderedProperties getInstance(String propertyFileName) {
                  if (instance == null) {
                      instance = new OrderedProperties();
                      readPropertiesFile(propertyFileName);
                  }
                  return instance;
              }
          
              private static void readPropertiesFile(String propertyFileName){
                  LineIterator lineIterator = null;
                  try {
          
                      //read file from classpath
                      URL url = instance.getClass().getResource(propertyFileName);
          
                      lineIterator = FileUtils.lineIterator(new File(url.getFile()), "UTF-8");
                      while (lineIterator.hasNext()) {
                          String line = lineIterator.nextLine();
          
                          //Continue to parse if there are blank lines (prevents IndesOutOfBoundsException)
                          if (!line.trim().isEmpty()) {
                              List<String> keyValuesPairs = Arrays.asList(line.split("="));
                              properties.put(keyValuesPairs.get(0) , keyValuesPairs.get(1));
                          }
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  } finally {
                      lineIterator.close();
                  }
              }
          
              public Map<String, String> getProperties() {
                  return OrderedProperties.properties;
              }
          
              public String getProperty(String key) {
                  return OrderedProperties.properties.get(key);
              }
          
          }
          

          使用方法:

              OrderedProperties o = OrderedProperties.getInstance("/project.properties");
              System.out.println(o.getProperty("test"));
          

          示例属性文件(在本例中为 project.properties):

          test=test2
          

          【讨论】:

          • 这种方法的问题是,原来的Properties类在加载时支持的不仅仅是简单的例子“test=test2”。例如,数据可以有“=”,您可以对特殊字符使用转义符等。编写自己的类意味着您必须实现更多。
          【解决方案9】:

          请参阅https://github.com/etiennestuder/java-ordered-properties 了解允许以明确定义的顺序读取/写入属性文件的完整实现。

          OrderedProperties properties = new OrderedProperties();
          properties.load(new FileInputStream(new File("~/some.properties")));
          

          【讨论】:

            【解决方案10】:

            我将添加一个更著名的 YAEOOJP(有序 Java 属性的另一个示例),因为似乎没有人会关心你的 默认 属性可以喂给你的属性。

            @见http://docs.oracle.com/javase/tutorial/essential/environment/properties.html

            那是我的课:肯定不是 1016% 符合任何可能的情况,但这对于我现在有限的愚蠢目的来说很好。感谢任何进一步的纠正意见,以便更大的利益可以受益。

            import java.util.Collections;
            import java.util.Enumeration;
            import java.util.LinkedHashSet;
            import java.util.Map;
            import java.util.Properties;
            import java.util.Set;
            
            /**
             * Remember javadocs  >:o
             */
            public class LinkedProperties extends Properties {
            
                protected LinkedProperties linkedDefaults;
                protected Set<Object> linkedKeys = new LinkedHashSet<>();
            
                public LinkedProperties() { super(); }
            
                public LinkedProperties(LinkedProperties defaultProps) {
                    super(defaultProps); // super.defaults = defaultProps;
                    this.linkedDefaults = defaultProps;
                }
            
                @Override
                public synchronized Enumeration<?> propertyNames() {
                    return keys();
                }
            
                @Override
                public Enumeration<Object> keys() {
                    Set<Object> allKeys = new LinkedHashSet<>();
                    if (null != defaults) {
                        allKeys.addAll(linkedDefaults.linkedKeys);
                    }
                    allKeys.addAll(this.linkedKeys);
                    return Collections.enumeration(allKeys);
                }
            
                @Override
                public synchronized Object put(Object key, Object value) {
                    linkedKeys.add(key);
                    return super.put(key, value);
                }
            
                @Override
                public synchronized Object remove(Object key) {
                    linkedKeys.remove(key);
                    return super.remove(key);
                }
            
                @Override
                public synchronized void putAll(Map<?, ?> values) {
                    for (Object key : values.keySet()) {
                        linkedKeys.add(key);
                    }
                    super.putAll(values);
                }
            
                @Override
                public synchronized void clear() {
                    super.clear();
                    linkedKeys.clear();
                }
            
                private static final long serialVersionUID = 0xC00L;
            }
            

            【讨论】:

            • 谢谢!!!!它解决了我的问题,以与文件中存在的顺序相同的顺序保存。
            【解决方案11】:
            Map<String, String> mapFile = new LinkedHashMap<String, String>();
            ResourceBundle bundle = ResourceBundle.getBundle(fileName);
            TreeSet<String> keySet = new TreeSet<String>(bundle.keySet());
            for(String key : keySet){
                System.out.println(key+" "+bundle.getString(key));
                mapFile.put(key, bundle.getString(key));
            }
            

            这会保持属性文件的顺序

            【讨论】:

              【解决方案12】:

              在我看来,PropertiesHashtable 有很大关系。我建议阅读它以获取LinkedHashMap。为此,您只需要重写一个方法 Object put(Object key, Object value)忽略 Properties 作为键/值容器:

              public class InOrderPropertiesLoader<T extends Map<String, String>> {
              
                  private final T map;
              
                  private final Properties properties = new Properties() {
                      public Object put(Object key, Object value) {
                          map.put((String) key, (String) value);
                          return null;
                      }
              
                  };
              
                  public InOrderPropertiesLoader(T map) {
                      this.map = map;
                  }
              
                  public synchronized T load(InputStream inStream) throws IOException {
                      properties.load(inStream);
              
                      return map;
                  }
              }
              

              用法:

              LinkedHashMap<String, String> props = new LinkedHashMap<>();
              try (InputStream inputStream = new FileInputStream(file)) {
                  new InOrderPropertiesLoader<>(props).load(inputStream);
              }
              

              【讨论】:

                【解决方案13】:

                some 的答案中,假设从文件中读取的属性被放入 Properties 的实例中(通过调用 put),以便它们出现在文件中。虽然这通常是它的行为方式,但我看不到这种顺序的任何保证。

                恕我直言:最好是逐行读取文件(以保证顺序),而不是将 Properties 类用作单个属性的解析器 行,最后将其存储在一些有序的集合中,如LinkedHashMap

                可以这样实现:

                private LinkedHashMap<String, String> readPropertiesInOrderFrom(InputStream propertiesFileInputStream)
                                                                           throws IOException {
                    if (propertiesFileInputStream == null) {
                      return new LinkedHashMap(0);
                    }
                
                    LinkedHashMap<String, String> orderedProperties = new LinkedHashMap<String, String>();
                
                    final Properties properties = new Properties(); // use only as a parser
                    final BufferedReader reader = new BufferedReader(new InputStreamReader(propertiesFileInputStream));
                
                    String rawLine = reader.readLine();
                
                    while (rawLine != null) {
                      final ByteArrayInputStream lineStream = new ByteArrayInputStream(rawLine.getBytes("ISO-8859-1"));
                      properties.load(lineStream); // load only one line, so there is no problem with mixing the order in which "put" method is called
                
                
                      final Enumeration<?> propertyNames = properties.<String>propertyNames();
                
                      if (propertyNames.hasMoreElements()) { // need to check because there can be empty or not parsable line for example
                
                        final String parsedKey = (String) propertyNames.nextElement();
                        final String parsedValue = properties.getProperty(parsedKey);
                
                        orderedProperties.put(parsedKey, parsedValue);
                        properties.clear(); // make sure next iteration of while loop does not access current property
                      }
                
                      rawLine = reader.readLine();
                    }
                
                    return orderedProperties;
                
                  }
                

                请注意,上面发布的方法需要一个InputStream,之后应该关闭它(当然,将它重写为只接受一个文件作为参数是没有问题的)。

                【讨论】:

                  【解决方案14】:

                  工作示例:

                  Map<String,String> properties = getOrderedProperties(new FileInputStream(new File("./a.properties")));
                  properties.entrySet().forEach(System.out::println);
                  

                  代码

                  public Map<String, String> getOrderedProperties(InputStream in) throws IOException{
                      Map<String, String> mp = new LinkedHashMap<>();
                      (new Properties(){
                          public synchronized Object put(Object key, Object value) {
                              return mp.put((String) key, (String) value);
                          }
                      }).load(in);
                      return mp;
                  }
                  

                  【讨论】:

                    【解决方案15】:

                    对于最近阅读此主题的人: 只需使用 org.apache.commons:commons-configuration2 中的类 PropertiesConfiguration。 我已经测试过它保持属性排序(因为它在内部使用 LinkedHashMap)。 正在做:

                    `
                        PropertiesConfiguration properties = new PropertiesConfiguration();
                        properties.read(new FileReader("/some/path));
                        properties.write(new FileWriter("/some/other/path"));
                    `
                    

                    只删除尾随空格和不必要的转义。

                    【讨论】:

                      猜你喜欢
                      • 2020-12-27
                      • 1970-01-01
                      • 2018-05-02
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多