[英]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.