当我运行以下程序时
System.out.println("Elements Via constructor Via offer()");
System.out.println(" Add Then-remove Add Then-remove");
for(int size: new int[] { 10, 100, 1_000, 10_000, 100_000, 1_000_000 }) {
int[] comparisons = { 0 };
class Element implements Comparable<Element> {
int x;
Element(int x) { this.x = x; }
public int compareTo(Element o) {comparisons[0]++;return Integer.compare(x,o.x);}
@Override public String toString() { return String.valueOf(x); }
}
List<Element> samples = IntStream.range(0, size)
.mapToObj(Element::new)
.collect(Collectors.toCollection(ArrayList::new));
Collections.shuffle(samples, ThreadLocalRandom.current());
System.out.printf("%8d", samples.size());
PriorityQueue<Element> q1 = new PriorityQueue<>(samples);
System.out.printf("%10d", comparisons[0]);
comparisons[0] = 0;
while(!q1.isEmpty()) q1.remove();
System.out.printf("%12d", comparisons[0]);
comparisons[0] = 0;
PriorityQueue<Element> q2 = new PriorityQueue<>(Comparator.naturalOrder());
for(Element e: samples) q2.offer(e);
System.out.printf("%10d", comparisons[0]);
comparisons[0] = 0;
while(!q2.isEmpty()) q2.remove();
System.out.printf("%12d", comparisons[0]);
System.out.println();
}
我反复得到结果
Elements Via constructor Via offer()
Add Then-remove Add Then-remove
10 13 27 13 27
100 169 850 205 850
1000 1863 14949 2264 14947
10000 18820 216653 22696 216736
100000 188046 2831466 228471 2831779
1000000 1881455 34913307 2281512 34914028
表明通过offer 添加元素确实执行了更多比较,但仍然具有 O(n) 时间复杂度。由于检索元素的复杂度为 O(n log n),因此构造上的差异相形见绌。
进一步注意,如果没有随机播放,即使用预先排序的数据,这两种方法根本没有区别。在实践中,数据可能介于排序和完全随机之间。
所以对于大多数实际目的来说,答案是这无关紧要。
如果您真的需要在受控环境中发挥最大性能,您可以使用 hack:
Comparator<Element> comparator = Comparator.naturalOrder();
PriorityQueue<Element> q = new PriorityQueue<>(
new PriorityQueue<>(comparator) { // not really recommended
@Override
public Object[] toArray() {
return samples.toArray();
}
});
PriorityQueue 将从输入 PriorityQueue 中获取比较器,但在 getClass() != PriorityQueue.class 时不信任 toArray() 结果中元素的顺序,如本例所示,并回退到将任意集合传递给构造函数时使用的相同例程。最终结果是设置比较器并使用任意集合输入进行初始化的行为。
当然,对于您打算提供给其他人的任何代码,不建议依赖此类实现细节。但除非未来版本添加 PriorityQueue(Collection<? extends E> c, Comparator<? super E> comparator) 构造函数,否则这是将两者都放入构造函数的唯一选择。
但如本答案的第一部分所示,您可能并不需要此 hack,因为通过 offer 添加所有元素也具有合理的性能。