Pulling values from a Java Properties file in orde

2020-01-30 06:29发布

I have a properties file where the order of the values is important. I want to be able to iterate through the properties file and output the values based on the order of the original file.

However, since the Properties file is backed by, correct me if I'm wrong, a Map that does not maintain insertion order, the iterator returns the values in the wrong order.

Here is the code I'm using

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

Is there anyway to get the Properties back in order short of writting my own custom file parser?

15条回答
Melony?
2楼-- · 2020-01-30 06:44

In some answers it is assumed that properties read from file are put to instance of Properties (by calls to put) in order they appear they in file. While this is in general how it behaves I don't see any guarantee for such order.

IMHO: it is better to read the file line by line (so that the order is guaranteed), than use the Properties class just as a parser of single property line and finally store it in some ordered Collection like LinkedHashMap.

This can be achieved like this:

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;

  }

Just note that the method posted above takes an InputStream which should be closed afterwards (of course there is no problem to rewrite it to take just a file as an argument).

查看更多
对你真心纯属浪费
3楼-- · 2020-01-30 06:46

Nope - maps are inherently "unordered".

You could possibly create your own subclass of Properties which overrode setProperty and possibly put, but it would probably get very implementation-specific... Properties is a prime example of bad encapsulation. When I last wrote an extended version (about 10 years ago!) it ended up being hideous and definitely sensitive to the implementation details of Properties.

查看更多
姐就是有狂的资本
4楼-- · 2020-01-30 06:47

Extend java.util.Properties, override both put() and 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);
    }
}
查看更多
Anthone
5楼-- · 2020-01-30 06:50

Dominique Laurent's solution above works great for me. I also added the following method override:

public Set<String> stringPropertyNames() {
    Set<String> set = new LinkedHashSet<String>();

    for (Object key : this.keys) {
        set.add((String)key);
    }

    return set;
}

Probably not the most efficient, but it's only executed once in my servlet lifecycle.

Thanks Dominique!

查看更多
叼着烟拽天下
6楼-- · 2020-01-30 06:52

In the interest of completeness ...

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();
    }
}

I dont think the methods returning set should be overridden as a set by definition does not maintain insertion order

查看更多
手持菜刀,她持情操
7楼-- · 2020-01-30 06:54

As I see it, Properties is to much bound to Hashtable. I suggest reading it in order to a LinkedHashMap. For that you'll only need to override a single method, Object put(Object key, Object value), disregarding the Properties as a key/value container:

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

Usage:

LinkedHashMap<String, String> props = new LinkedHashMap<>();
try (InputStream inputStream = new FileInputStream(file)) {
    new InOrderPropertiesLoader<>(props).load(inputStream);
}
查看更多
登录 后发表回答