Java Collections Framework中常用方法(大小)的意外复杂性?
最近,我对一些Java集合没有方法size()的恒定时间操作这一事实感到惊讶。
虽然我了解到集合的并发实现做出了一些妥协,作为并发收益的权衡(在ConcurrentLinkedQueue,ConcurrentSkipListSet,LinkedTransferQueue等中,大小为O(n),但好消息是这在API文档中得到了适当的记录。
我关心的是某些集合的方法返回的视图上方法大小的性能。例如,TreeSet.tailSet 返回其元素大于或等于 fromElement 的支持集部分的视图。令我非常惊讶的是,返回的 SortedSet 上的调用大小在时间上是线性的,即 O(n)。至少这是我设法从OpenJDK的源代码中挖掘出来的:在TreeSet中,它是作为TreeMap上的包装器实现的,在TreeMap中,有一个EntrySetView类,其大小方法如下:
abstract class EntrySetView extends AbstractSet<Map.Entry<K,V>> {
private transient int size = -1, sizeModCount;
public int size() {
if (fromStart && toEnd)
return m.size();
if (size == -1 || sizeModCount != m.modCount) {
sizeModCount = m.modCount;
size = 0;
Iterator i = iterator();
while (i.hasNext()) {
size++;
i.next();
}
}
return size;
}
....
}
这意味着第一次调用 size 是 O(n),然后只要不修改后备映射,它就会被缓存。我无法在API文档中找到这一事实。更有效的实现是 O(log n),在子树大小的缓存中需要进行内存权衡。由于为了避免代码重复(TreeSet作为TreeMap的包装器),因此我看不出为什么出于性能原因不应该进行这样的权衡。
不考虑我对TreeSet的OpenJDK实现的(非常简短)分析的正确或错误,我想知道是否有关于许多此类操作性能的详细而完整的文档,特别是那些完全出乎意料的操作?