28

我正在使用 java.util.Properties 的 store(Writer, String) 方法来存储属性。在生成的文本文件中,属性以随意的顺序存储。

这就是我正在做的事情:

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

如何确保按字母顺序或按添加属性的顺序写出属性?

我希望有一个比“手动创建属性文件”更简单的解决方案。

4

8 回答 8

63

根据“新白痴”的建议,它按字母键顺序存储。

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);
于 2013-06-09T15:46:03.773 回答
10

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

OrderedProperties properties = new OrderedProperties();
properties.load(new FileInputStream(new File("~/some.properties")));
于 2014-12-12T16:32:01.153 回答
3

使用 aTreeSet是危险的!因为在CASE_INSENSITIVE_ORDER字符串“mykey”、“MyKey”和“MYKEY”中会产生相同的索引!(因此将省略 2 个键)。

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 );
于 2018-10-24T21:37:13.193 回答
3

Steve McLeod 的回答曾经对我有用,但从 Java 11 开始就没有了。

问题似乎是 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)));
        }
    };
}

我会警告说,这无论如何都不会很快。它强制对不理想的 LinkedHashSet 进行迭代,但我愿意接受建议。

于 2019-01-24T21:30:12.843 回答
2

尝试对不区分大小写进行排序时,Steve McLeod 的解决方案不起作用。

这就是我想出的

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);
    }
于 2017-01-17T09:38:22.740 回答
1

如果有人必须在 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() })
                    )
            )
        }

}
于 2021-09-02T19:39:11.070 回答
0

我也有同样的烦恼,所以我实现了一个简单的 kludge 子类,它允许您显式地预定义出现在一个块中的订单名称/值,并在另一个块中对它们进行词汇排序。

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

无论如何,您需要覆盖public Set<Map.Entry<Object, Object>> entrySet(),而不是public Enumeration<Object> keys(); 后者,正如https://stackoverflow.com/users/704335/timmos指出的那样,从不使用该store(..)方法。

于 2021-08-21T20:26:35.970 回答
0

如果您的属性文件很小,并且您想要一个面向未来的解决方案,那么我建议您将 Properties 对象存储在文件中并将文件加载回字符串(或将其存储到 ByteArrayOutputStream 并将其转换为字符串),将字符串拆分为行,对行进行排序,然后将行写入所需的目标文件。

这是因为 Properties 类的内部实现总是在变化的,而要实现 store() 中的排序,需要在不同的 Java 版本中重写 Properties 类的不同方法(参见How to sort Properties in java?)。如果您的属性文件不大,那么我更喜欢面向未来的解决方案,而不是最佳性能的解决方案。

对于将字符串拆分为行的正确方法,一些可靠的解决方案是:

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

而且您不必担心具有多行的值,因为 Properties.store() 会将整个多行字符串转义为输出文件中的一行。

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

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
    }
}
于 2021-09-01T10:14:49.567 回答