当我看到源代码: java.util.AbstractCollection.toArray()时,它实现如下:
public Object[] toArray() {
// Estimate size of array; be prepared to see more or fewer elements
Object[] r = new Object[size()];
Iterator<E> it = iterator();
for (int i = 0; i < r.length; i++) {
if (! it.hasNext()) // fewer elements than expected
return Arrays.copyOf(r, i);
r[i] = it.next();
}
return it.hasNext() ? finishToArray(r, it) : r;
}
private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
int i = r.length;
while (it.hasNext()) {
int cap = r.length;
if (i == cap) {
int newCap = cap + (cap >> 1) + 1;
// overflow-conscious code
if (newCap - MAX_ARRAY_SIZE > 0)
newCap = hugeCapacity(cap + 1);
r = Arrays.copyOf(r, newCap);
}
r[i++] = (T)it.next();
}
// trim if overallocated
return (i == r.length) ? r : Arrays.copyOf(r, i);
}
如您所见,实施起来并不容易理解,我的问题是:
答案 0 :(得分:3)
如您所见,实施并不那么容易理解,我的问题是:
- 在迭代期间,当集合的元素发生变化(大小未更改)时,我会得到什么?我猜迭代器可能是某种快照。
- 当收藏品的尺寸发生变化时,我会得到什么?我想知道它是否能正常工作。
醇>
实现是这样的,因为它旨在处理迭代器返回的元素数量不同于size()
的情况。如果集合的大小在迭代期间发生更改,则会发生这种情况。目标数组是基于size()
分配的,而在乐观情况下,大小不会改变,它非常简单。代码的复杂性来自于迭代器返回的实际元素数与size()
返回的初始值不同。如果元素的实际数量较小,则将元素复制到正确大小的较小数组中。如果实际数字更大,则将元素复制到更大的数组中,然后迭代更多元素。如果数组填满,则重复重新分配数组,直到迭代完成。
对于第一个问题,迭代器不一定要拍摄元素的快照。这取决于实际的集合实现。某些集合(例如CopyOnWriteArrayList
)确实具有快照语义,因此如果修改了集合,则迭代器将无法看到修改。在这种情况下,迭代器报告的元素数量将匹配size()
,因此不需要重新分配数组。
如果在迭代期间修改了集合,则其他集合实现具有不同的策略。有些是故障快速,这意味着他们会抛出ConcurrentModificationException
。其他的弱一致,这意味着迭代器可能会或可能不会看到修改。
这适用于您的第二个问题。如果集合大小在迭代期间发生变化,并且该集合的迭代器支持这一点(即,它不是快速失败),则此处的代码将处理来自迭代器的不同数量的元素,而不是size()
最初报告的元素数量。
可能发生这种情况的一个例子是ConcurrentSkipListSet
。这个类的迭代器是弱一致的,它继承了toArray()
的{{1}}方法。因此,虽然AbstractCollection
正在迭代集合以便将元素收集到目标数组中,但是另一个线程修改集合完全合法,可能会改变它的大小。这显然会导致迭代器报告toArray()
返回的初始值中不同数量的元素,这将导致size()
中的数组重新分配代码被执行。
答案 1 :(得分:0)
当集合的大小发生变化时,我会得到什么?
return Arrays.copyOf(r, i)
方法中使用toArray()
“缩小”,如评论所示。it.hasNext() ? finishToArray(r, it) : r
调用将处理该案例。 finishToArray
方法继续将元素添加到数组中并在需要时“扩展”其大小:计算新容量(newCap = cap + (cap >> 1) + 1
)并且数组“展开”(r = Arrays.copyOf(r, newCap)
)。答案 2 :(得分:0)
我不认为所有Collection实现都是线程安全的,而不是担心你可以使用以下方式使你的Collection同步:
Collections.synchronizedCollection(myCollection);
或者你可以看看:
https://docs.oracle.com/javase/tutorial/essential/concurrency/collections.html
编辑: Here我找到了一个很好的解释
答案 3 :(得分:0)
您只能确定迭代的结果是未定义的(除非您知道正在使用的集合的确切实现)。通常会抛出ConcurrentModificationException
,但你不能依赖这个假设。
如果在迭代时修改Collection
,则在大多数实现中,抛出ConcurrentModificationException
。这样做的Iterators
称为失败快速迭代器。
但这取决于每个实现,尽管JRE提供的所有通用集合实现都是这样,但并非所有Iterators
都是快速失败的。并且还要注意,无法保证快速失败的行为,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬性保证。
为什么在Java中这样实现了这样的实现?
因为此实现假定集合的大小可以随时更改,因为迭代器可能不会抛出任何异常。 因此,此方法检查迭代器是否可以提供比初始估计大小更多或更少的元素。