按顺序从 Java 属性文件中提取值?

Posted

技术标签:

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

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

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

这是我正在使用的代码

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

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

【问题讨论】:

【参考方案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"));
`

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

【讨论】:

以上是关于按顺序从 Java 属性文件中提取值?的主要内容,如果未能解决你的问题,请参考以下文章

java程序执行顺序

如何按定义的顺序编写 Java 属性?

从照片中提取后,视频及其缩略图未按顺序保存

按特定顺序从 ResultSet 检索和显示值

如何按特定顺序从 select 中执行 Oracle SQL 更新?

使数据库中的数据按照一定的顺序查出来