简体   繁体   English

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

[英]How can I write Java properties in a defined order?

I'm using java.util.Properties's store(Writer, String) method to store the properties.我正在使用 java.util.Properties 的 store(Writer, String) 方法来存储属性。 In the resulting text file, the properties are stored in a haphazard order.在生成的文本文件中,属性以随意的顺序存储。

This is what I'm doing:这就是我正在做的事情:

Properties properties = createProperties();
properties.store(new FileWriter(file), null);

How can I ensure the properties are written out in alphabetical order, or in the order the properties were added?如何确保按字母顺序或按添加属性的顺序写出属性?

I'm hoping for a solution simpler than "manually create the properties file".我希望有一个比“手动创建属性文件”更简单的解决方案。

As per "The New Idiot's" suggestion, this stores in alphabetical key order.根据“新白痴”的建议,它按字母键顺序存储。

Properties tmp = new Properties() {
    @Override
    public synchronized Enumeration<Object> keys() {
        return Collections.enumeration(new TreeSet<Object>(super.keySet()));
    }
};
tmp.putAll(properties);
tmp.store(new FileWriter(file), null);

See https://github.com/etiennestuder/java-ordered-properties for a complete implementation that allows to read/write properties files in a well-defined order.有关允许以明确定义的顺序读取/写入属性文件的完整实现,请参阅https://github.com/etiennestuder/java-ordered-properties

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

Steve McLeod's answer used to work for me, but since Java 11, it doesn't. Steve McLeod 的回答曾经对我有用,但从 Java 11 开始就没有了。

The problem seemed to be EntrySet ordering, so, here you go:问题似乎是 EntrySet 排序,所以,你去:

@SuppressWarnings("serial")
private static Properties newOrderedProperties() 
{
    return new Properties() {
        @Override public synchronized Set<Map.Entry<Object, Object>> entrySet() {
            return Collections.synchronizedSet(
                    super.entrySet()
                    .stream()
                    .sorted(Comparator.comparing(e -> e.getKey().toString()))
                    .collect(Collectors.toCollection(LinkedHashSet::new)));
        }
    };
}

I will warn that this is not fast by any means.我会警告说,这无论如何都不会很快。 It forces iteration over a LinkedHashSet which isn't ideal, but I'm open to suggestions.它强制对不理想的 LinkedHashSet 进行迭代,但我愿意接受建议。

To use a TreeSet is dangerous!使用TreeSet是危险的! Because in the CASE_INSENSITIVE_ORDER the strings "mykey", "MyKey" and "MYKEY" will result in the same index!因为在CASE_INSENSITIVE_ORDER中,字符串“mykey”、“MyKey”和“MYKEY”将产生相同的索引! (so 2 keys will be omitted). (因此将省略 2 个键)。

I use List instead, to be sure to keep all keys.我使用List代替,以确保保留所有键。

 List<Object> list = new ArrayList<>( super.keySet());
 Comparator<Object> comparator = Comparator.comparing( Object::toString, String.CASE_INSENSITIVE_ORDER );
 Collections.sort( list, comparator );
 return Collections.enumeration( list );

The solution from Steve McLeod did not not work when trying to sort case insensitive.尝试对不区分大小写进行排序时,Steve McLeod 的解决方案不起作用。

This is what I came up with这就是我想出的

Properties newProperties = new Properties() {

    private static final long serialVersionUID = 4112578634029874840L;

    @Override
    public synchronized Enumeration<Object> keys() {
        Comparator<Object> byCaseInsensitiveString = Comparator.comparing(Object::toString,
                        String.CASE_INSENSITIVE_ORDER);

        Supplier<TreeSet<Object>> supplier = () -> new TreeSet<>(byCaseInsensitiveString);

        TreeSet<Object> sortedSet = super.keySet().stream()
                        .collect(Collectors.toCollection(supplier));

        return Collections.enumeration(sortedSet);
    }
 };

    // propertyMap is a simple LinkedHashMap<String,String>
    newProperties.putAll(propertyMap);
    File file = new File(filepath);
    try (FileOutputStream fileOutputStream = new FileOutputStream(file, false)) {
        newProperties.store(fileOutputStream, null);
    }

In case someone has to do this in kotlin:如果有人必须在 kotlin 中执行此操作:

class OrderedProperties: Properties() {

    override val entries: MutableSet<MutableMap.MutableEntry<Any, Any>>
        get(){
            return Collections.synchronizedSet(
                super.entries
                    .stream()
                    .sorted(Comparator.comparing { e -> e.key.toString() })
                    .collect(
                        Collectors.toCollection(
                            Supplier { LinkedHashSet() })
                    )
            )
        }

}

I'm having the same itch, so I implemented a simple kludge subclass that allows you to explicitly pre-define the order name/values appear in one block and lexically order them in another block.我也有同样的烦恼,所以我实现了一个简单的 kludge 子类,它允许您显式地预定义出现在一个块中的订单名称/值,并在另一个块中对它们进行词汇排序。

https://github.com/crums-io/io-util/blob/master/src/main/java/io/crums/util/TidyProperties.java https://github.com/crums-io/io-util/blob/master/src/main/java/io/crums/util/TidyProperties.java

In any event, you need to override public Set<Map.Entry<Object, Object>> entrySet() , not public Enumeration<Object> keys() ;无论如何,您需要覆盖public Set<Map.Entry<Object, Object>> entrySet() ,而不是public Enumeration<Object> keys() the latter, as https://stackoverflow.com/users/704335/timmos points out, never hits on the store(..) method.后者,正如https://stackoverflow.com/users/704335/timmos指出的那样,从不使用store(..)方法。

If your properties file is small, and you want a future-proof solution, then I suggest you to store the Properties object on a file and load the file back to a String (or store it to ByteArrayOutputStream and convert it to a String), split the string into lines, sort the lines, and write the lines to the destination file you want.如果您的属性文件很小,并且您想要一个面向未来的解决方案,那么我建议您将 Properties 对象存储在文件中并将文件加载回字符串(或将其存储到 ByteArrayOutputStream 并将其转换为字符串),将字符串拆分为行,对行进行排序,然后将行写入所需的目标文件。

It's because the internal implementation of Properties class is always changing, and to achieve the sorting in store(), you need to override different methods of Properties class in different versions of Java (see How to sort Properties in java? ).这是因为 Properties 类的内部实现总是在变化的,而要实现 store() 中的排序,需要在不同的 Java 版本中重写 Properties 类的不同方法(参见How to sort Properties in java? )。 If your properties file is not large, then I prefer a future-proof solution over the best performance one.如果您的属性文件不大,那么我更喜欢面向未来的解决方案,而不是最佳性能的解决方案。

For the correct way to split the string into lines, some reliable solutions are:对于将字符串拆分为行的正确方法,一些可靠的解决方案是:

  • Files.lines()/Files.readAllLines(), if you use a File Files.lines()/Files.readAllLines(),如果你使用 File
  • BufferedReader.readLine() (Java 7 or earlier) BufferedReader.readLine()(Java 7 或更早版本)
  • IOUtils.readLines(bufferedReader) (org.apache.commons.io.IOUtils, Java 7 or earlier) IOUtils.readLines(bufferedReader)(org.apache.commons.io.IOUtils,Java 7 或更早版本)
  • BufferedReader.lines() (Java 8+) as mentioned in Split Java String by New Line BufferedReader.lines() (Java 8+),如按换行符拆分 Java 字符串中所述
  • String.lines() (Java 11+) as mentioned in Split Java String by New Line . String.lines() (Java 11+) 如Split Java String by New Line中所述。

And you don't need to be worried about values with multiple lines, because Properties.store() will escape the whole multi-line String into one line in the output file.而且您不必担心具有多行的值,因为 Properties.store() 会将整个多行字符串转义为输出文件中的一行。

Sample codes for Java 8: Java 8 的示例代码:

public static void test() {
    ......
    String comments = "Your multiline comments, this should be line 1." +
            "\n" +
            "The sorting should not mess up the comment lines' ordering, this should be line 2 even if T is smaller than Y";

    saveSortedPropertiesToFile(inputProperties, comments, Paths.get("C:\\dev\\sorted.properties"));
}

public static void saveSortedPropertiesToFile(Properties properties, String comments, Path destination) {
    try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
        // Storing it to output stream is the only way to make sure correct encoding is used.
        properties.store(outputStream, comments);

        /* The encoding here shouldn't matter, since you are not going to modify the contents,
           and you are only going to split them to lines and reorder them.
           And Properties.store(OutputStream, String) should have translated unicode characters into (backslash)uXXXX anyway.
         */
        String propertiesContentUnsorted = outputStream.toString("UTF-8");

        String propertiesContentSorted;
        try (BufferedReader bufferedReader = new BufferedReader(new StringReader(propertiesContentUnsorted))) {
            List<String> commentLines = new ArrayList<>();
            List<String> contentLines = new ArrayList<>();

            boolean commentSectionEnded = false;
            for (Iterator<String> it = bufferedReader.lines().iterator(); it.hasNext(); ) {
                String line = it.next();
                if (!commentSectionEnded) {
                    if (line.startsWith("#")) {
                        commentLines.add(line);
                    } else {
                        contentLines.add(line);
                        commentSectionEnded = true;
                    }
                } else {
                    contentLines.add(line);
                }
            }
            // Sort on content lines only
            propertiesContentSorted = Stream.concat(commentLines.stream(), contentLines.stream().sorted())
                    .collect(Collectors.joining(System.lineSeparator()));
        }

        // Just make sure you use the same encoding as above.
        Files.write(destination, propertiesContentSorted.getBytes(StandardCharsets.UTF_8));

    } catch (IOException e) {
        // Log it if necessary
    }
}

Sample codes for Java 7: Java 7 的示例代码:

import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

......

public static void test() {
    ......
    String comments = "Your multiline comments, this should be line 1." +
            "\n" +
            "The sorting should not mess up the comment lines' ordering, this should be line 2 even if T is smaller than Y";

    saveSortedPropertiesToFile(inputProperties, comments, Paths.get("C:\\dev\\sorted.properties"));
}

public static void saveSortedPropertiesToFile(Properties properties, String comments, Path destination) {
    try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
        // Storing it to output stream is the only way to make sure correct encoding is used.
        properties.store(outputStream, comments);

        /* The encoding here shouldn't matter, since you are not going to modify the contents,
           and you are only going to split them to lines and reorder them.
           And Properties.store(OutputStream, String) should have translated unicode characters into (backslash)uXXXX anyway.
         */
        String propertiesContentUnsorted = outputStream.toString("UTF-8");

        String propertiesContentSorted;
        try (BufferedReader bufferedReader = new BufferedReader(new StringReader(propertiesContentUnsorted))) {
            List<String> commentLines = new ArrayList<>();
            List<String> contentLines = new ArrayList<>();

            boolean commentSectionEnded = false;

            for (Iterator<String> it = IOUtils.readLines(bufferedReader).iterator(); it.hasNext(); ) {
                String line = it.next();
                if (!commentSectionEnded) {
                    if (line.startsWith("#")) {
                        commentLines.add(line);
                    } else {
                        contentLines.add(line);
                        commentSectionEnded = true;
                    }
                } else {
                    contentLines.add(line);
                }
            }
            // Sort on content lines only
            Collections.sort(contentLines);

            propertiesContentSorted = StringUtils.join(IterableUtils.chainedIterable(commentLines, contentLines).iterator(), System.lineSeparator());
        }

        // Just make sure you use the same encoding as above.
        Files.write(destination, propertiesContentSorted.getBytes(StandardCharsets.UTF_8));

    } catch (IOException e) {
        // Log it if necessary
    }
}

True that keys() is not triggered so instead of passing trough a list as Timmos suggested you can do it like this:确实,keys() 没有被触发,所以不是像 Timmos 建议的那样通过列表传递,你可以这样做:

Properties alphaproperties = new Properties() {
        @Override
        public Set<Map.Entry<Object, Object>> entrySet() {
            Set<Map.Entry<Object, Object>> setnontrie = super.entrySet();
            Set<Map.Entry<Object, Object>> unSetTrie = new ConcurrentSkipListSet<Map.Entry<Object, Object>>(new Comparator<Map.Entry<Object, Object>>() {
                @Override
                public int compare(Map.Entry<Object, Object> o1, Map.Entry<Object, Object> o2) {
                    return o1.getKey().toString().compareTo(o2.getKey().toString());
                }
            });
            unSetTrie.addAll(setnontrie);
            return unSetTrie;
            }
    };
    alphaproperties.putAll(properties);
    alphaproperties.store(fw, "UpdatedBy Me");
    fw.close();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM