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

Posted

技术标签:

【中文标题】如何按定义的顺序编写 Java 属性?【英文标题】:How can I write Java properties in a defined order? 【发布时间】:2013-06-05 08:30:59 【问题描述】:

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

这就是我正在做的:

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

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

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

【问题讨论】:

这个answer可以帮忙吗? 只覆盖public synchronized Enumeration<K> keys() 而不是keySet() 不过,这不包括插入订单的情况。 你是对的。但是作为OP,我对字母顺序有点满意。 【参考方案1】:

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

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

【讨论】:

先大写再小写 我更喜欢 Etienne Studer 的回答,因为如果你想要插入顺序,你无法通过这种方式实现,因为它内部已经使用了 HashTable。在这种情况下,LinkedHashTable 或 LinkedHashSet 不会有任何效果。 我不认为这仍然有效? keys() 中的断点不会触发。我通过覆盖entrySet() 方法解决了这个问题。使用super.entrySet(),转换为列表,对列表进行排序,转换回Set并返回。【参考方案2】:

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

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

【讨论】:

你有没有检查过你在文件中存储了什么?我有一种感觉,您用于自定义属性的地图根本没有连接到文件,并且您在文件中存储了默认的 HashTable,如果我错了,对不起。 这是测试覆盖率:github.com/etiennestuder/java-ordered-properties/blob/master/… 是的,我明白了,感谢您的回答,我试图在 android 中使用它,但实际上 java Properties 在 Android 中有不同的实现,因此您还需要重写 entrySet() 方法来实现工作。 没有看到github上的链接的,maven神器可以看这里:bintray.com/etienne/java-utilities/java-ordered-properties【参考方案3】:

使用TreeSet 是危险的! 因为在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 );

【讨论】:

【参考方案4】:

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 进行迭代,但我愿意接受建议。

【讨论】:

【参考方案5】:

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

【讨论】:

keys() 方法的第 2 行和第 3 行可以替换为:Set&lt;Object&gt; sortedKeys = new TreeSet&lt;&gt;(byCaseInsensitiveString);,后跟:sortedKeys.addAll(super.keySet());【参考方案6】:

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


【讨论】:

【参考方案7】:

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

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

无论如何,你需要覆盖public Set&lt;Map.Entry&lt;Object, Object&gt;&gt; entrySet(),而不是public Enumeration&lt;Object&gt; keys();正如https://***.com/users/704335/timmos 指出的那样,后者永远不会使用store(..) 方法。

【讨论】:

【参考方案8】:

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

这是因为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+) 如Split Java String by New Line 中所述 Split Java String by New Line 中提到的 String.lines() (Java 11+)。

而且你不必担心多行的值,因为 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
    

【讨论】:

以上是关于如何按定义的顺序编写 Java 属性?的主要内容,如果未能解决你的问题,请参考以下文章

如何按给定顺序读取类的属性?

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

C# LINQ 组按属性集合,然后按列表定义的显式顺序对每个组进行排序[重复]

java程序执行顺序

基于请求的不同属性和基于java中的请求的不同顺序排序(不是重复的)

编写一个程序,使用 Java AWT 组件按选择顺序在列表中显示所选项目