繁体   English   中英

在比较器中使用列表时,排序ArrayList可能会失败。 这记录了吗?

[英]Sorting ArrayList can fail when using list in comparator. Is this documented?

ArrayLists似乎与TimSort一起排序,其中基础列表在排序期间并不总是一致的。 在调用比较器时,列表条目可能会消失或出现两次。

在我们的比较器中,我们比较了我们使用函数的键,以获得一个值来比较这个键。 由于此函数在其他上下文中使用,我们测试密钥是否实际存在于列表中(在排序中不需要的东西):

        if (keys.contains(itemId)) {
          ...

由于密钥是我们正在排序的列表,因此可能会在比较器中发生由于TimSort的内部机制而在列表中找不到密钥。

问题:这是在Javadoc中找到的(无法找到它)你不应该访问Comparator中的底层列表吗? 这是一个糟糕的TimSort实现应该排序副本吗? 或者首先在比较器中访问底层列表是一个愚蠢的想法?


TJ Crowder提供的以下程序表明,在调用比较器期间,基础列表的内容可能不一致。 (该程序演示了有问题的现象,但它并不代表受该问题影响的实际应用程序。)

import java.util.*;

public class Example {
    private static String[] chars = {
        "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
    };

    private List<String> list;
    private String[] entries;

    private Example() {
        this.entries = new String[1000];
        for (int n = 0; n < 1000; ++n) {
            this.entries[n] = chars[n % chars.length] + n;
        }
        // Ensure it's an ArrayList, specifically
        this.list = new ArrayList<String>(Arrays.asList(this.entries));
    }

    public static void main(String[] args) {
        (new Example()).run();
    }

    class ListComparator implements Comparator<String> {
        public int compare(String a, String b) {
            for (String s : entries) {
                int i1 = Example.this.list.indexOf(s);
                if (i1 == -1) {
                    System.out.println(s + ": Missing");
                } else {
                    int i2 = Example.this.list.lastIndexOf(s);
                    if (i2 != i1) {
                        System.out.println(s + ": Duplicated, at " + i1 + " and " + i2);
                    }
                }
            }
            return a.compareTo(b);
        }
    }

    private void run() {
        this.list.sort(new ListComparator());
    }
}

以下是运行的前几行输出:

b1: Missing
a52: Duplicated, at 2 and 32
b27: Missing
a52: Duplicated, at 2 and 32
c2: Missing
a52: Duplicated, at 2 and 32
c2: Missing
c28: Missing
a52: Duplicated, at 2 and 32
b53: Duplicated, at 5 and 33
c28: Missing
d29: Missing
a52: Duplicated, at 2 and 32
b53: Duplicated, at 5 and 33
d3: Missing
d29: Missing
a52: Duplicated, at 2 and 32
b53: Duplicated, at 5 and 33
d3: Missing
d29: Missing
e30: Missing

这里有一点历史:在JDK 7中,TimSort算法取代了之前的“遗留合并排序”算法。 在JDK 8中, Collections.sort()委托新的默认方法List.sort() ArrayList会覆盖此默认方法,它会就地进行排序。 以前的Collections.sort()实现会将列表复制到临时数组,对该临时数组执行排序,然后将临时数组中的元素复制回原始列表。

如果排序比较器在要排序的列表中查找,那么它的行为肯定会受到JDK 8中引入的ArrayList的新就地排序行为的影响。从“遗留合并排序”到JDK 7中的TimSort的更改可能没有在这种情况下的影响,因为JDK 7仍然在临时副本上进行排序。

List.sort()的copy-sort- List.sort()行为在“Implementation Requirements”部分中描述,该部分指定了默认方法实现的行为,但它不是所有实现的接口契约的一部分。 因此, ArrayList (和其他子类)可以自由地改变这种行为。 我注意到没有重写实现ArrayList.sort()文档。 我想如果添加了一些指定了就地排序行为的文档,那将是一个很小的改进。

如果ArrayList的就地排序有问题,您可以在排序之前复制列表:

List<Key> list = ... ;
List<Key> newList = new ArrayList<>(list);
newList.sort(keyComparator); // uses the old list
list = newList;

或者,也许您可​​以提供有关比较器工作方式的更多详细信息,并且我们可能能够找到一种方法来重写它,以便它不需要查看正在排序的列表。 (我建议再问这个问题。)

暂无
暂无

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

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