为什么toArray在java中是这样实现的?
Why is toArray implemented like this in java?
我看到源码: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);
}
如你所见,实现不是那么容易理解,我的问题是:
- 当集合的元素在迭代过程中发生变化(大小不变)时,我会得到什么?我猜迭代器可能是某种快照。
- 当集合的大小改变时我会得到什么?不知道能不能正常使用
What will I get when the collection's size changed?
- 如果集合的大小小于预期,则数组为 "reduced",在
toArray()
方法中使用 return Arrays.copyOf(r, i)
,如注释所示。
- 如果集合的大小超过预期,
it.hasNext() ? finishToArray(r, it) : r
调用会处理这种情况。 finishToArray
方法继续向数组中添加元素,如果需要 "expand" 其大小:计算新容量 (newCap = cap + (cap >> 1) + 1
) 并且数组为 "expanded" (r = Arrays.copyOf(r, newCap)
) .
我认为并非所有 Collection 实现都是线程安全的,不用担心,您可以使用以下方法使您的 Collection 同步:
Collections.synchronizedCollection(myCollection);
或者你可以看看:
https://docs.oracle.com/javase/tutorial/essential/concurrency/collections.html
编辑:
Here 我找到了一个很好的解释
您只能确定迭代的结果是未定义的(除非您知道正在使用的集合的确切实现)。通常会抛出 ConcurrentModificationException
,但您不能依赖该假设。
如果 Collection
在遍历它时被修改,在大多数实现中,会抛出 ConcurrentModificationException
。 Iterators
这样做被称为 fail-fast 迭代器。
但这取决于每个实现,尽管 JRE 提供的所有通用集合实现都这样做,但并非所有 Iterators
都是 fail-fast。还要注意 fail-fast 行为无法得到保证,因为一般来说,在存在非同步并发修改的情况下不可能做出任何硬性保证。
Why is toArray implemented like this in java?
因为此实现假定集合的大小可以随时更改,因为迭代器可能不会抛出任何异常。
因此,此方法检查迭代器提供的元素可能多于或少于初始估计大小。
As you see,the implementation is not so easy to understand, my question is :
- What will I get when the collection's elements change (size not changed) during iteration? I guess the iterator may be some kind of snapshot.
- What will I get when the collection's size is changed? I wonder if it can work correctly.
实现是这样的,因为它旨在处理迭代器 returns 与 size()
不同数量的元素的情况。如果集合的大小在迭代过程中发生变化,就会发生这种情况。目标数组是根据 size()
分配的,在大小不变的乐观情况下,它非常简单。代码的复杂性在于迭代器返回的实际元素数与 size()
返回的初始值不同。如果元素的实际数量较少,则将元素复制到大小合适的较小数组中。如果实际数字更大,则将元素复制到更大的数组中,然后迭代更多元素。如果数组填满,则会重复重新分配更大的数组,直到迭代完成。
对于您的第一个问题,迭代器不一定对元素进行快照。这取决于实际的集合实现。一些集合(比如 CopyOnWriteArrayList
)确实有快照语义,所以如果集合被修改,修改对迭代器是不可见的。在这种情况下,迭代器报告的元素数将匹配 size()
,因此不需要重新分配数组。
其他集合实现对于在迭代期间修改集合时会发生什么有不同的策略。有些是 fail-fast,这意味着他们会抛出 ConcurrentModificationException
。其他的弱一致,这意味着迭代器可能会或可能不会看到修改。
这适用于你的第二个问题。如果集合大小在迭代过程中发生变化,并且该集合的迭代器支持此功能(即,它不是 fail-fast),则此处的代码将处理迭代器中出现的元素数量与 [=10 最初报告的数量不同=].
一个可能发生这种情况的例子是 ConcurrentSkipListSet
。这个class的迭代器是弱一致的,它继承了AbstractCollection
的toArray()
方法。因此,当 toArray()
迭代集合以将元素收集到目标数组中时,另一个线程修改集合(可能会更改其大小)是完全合法的。这显然会导致迭代器报告与 size()
返回的初始值不同的元素数,这将导致执行 toArray()
中的数组重新分配代码。
我看到源码: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);
}
如你所见,实现不是那么容易理解,我的问题是:
- 当集合的元素在迭代过程中发生变化(大小不变)时,我会得到什么?我猜迭代器可能是某种快照。
- 当集合的大小改变时我会得到什么?不知道能不能正常使用
What will I get when the collection's size changed?
- 如果集合的大小小于预期,则数组为 "reduced",在
toArray()
方法中使用return Arrays.copyOf(r, i)
,如注释所示。 - 如果集合的大小超过预期,
it.hasNext() ? finishToArray(r, it) : r
调用会处理这种情况。finishToArray
方法继续向数组中添加元素,如果需要 "expand" 其大小:计算新容量 (newCap = cap + (cap >> 1) + 1
) 并且数组为 "expanded" (r = Arrays.copyOf(r, newCap)
) .
我认为并非所有 Collection 实现都是线程安全的,不用担心,您可以使用以下方法使您的 Collection 同步:
Collections.synchronizedCollection(myCollection);
或者你可以看看:
https://docs.oracle.com/javase/tutorial/essential/concurrency/collections.html
编辑: Here 我找到了一个很好的解释
您只能确定迭代的结果是未定义的(除非您知道正在使用的集合的确切实现)。通常会抛出 ConcurrentModificationException
,但您不能依赖该假设。
如果 Collection
在遍历它时被修改,在大多数实现中,会抛出 ConcurrentModificationException
。 Iterators
这样做被称为 fail-fast 迭代器。
但这取决于每个实现,尽管 JRE 提供的所有通用集合实现都这样做,但并非所有 Iterators
都是 fail-fast。还要注意 fail-fast 行为无法得到保证,因为一般来说,在存在非同步并发修改的情况下不可能做出任何硬性保证。
Why is toArray implemented like this in java?
因为此实现假定集合的大小可以随时更改,因为迭代器可能不会抛出任何异常。 因此,此方法检查迭代器提供的元素可能多于或少于初始估计大小。
As you see,the implementation is not so easy to understand, my question is :
- What will I get when the collection's elements change (size not changed) during iteration? I guess the iterator may be some kind of snapshot.
- What will I get when the collection's size is changed? I wonder if it can work correctly.
实现是这样的,因为它旨在处理迭代器 returns 与 size()
不同数量的元素的情况。如果集合的大小在迭代过程中发生变化,就会发生这种情况。目标数组是根据 size()
分配的,在大小不变的乐观情况下,它非常简单。代码的复杂性在于迭代器返回的实际元素数与 size()
返回的初始值不同。如果元素的实际数量较少,则将元素复制到大小合适的较小数组中。如果实际数字更大,则将元素复制到更大的数组中,然后迭代更多元素。如果数组填满,则会重复重新分配更大的数组,直到迭代完成。
对于您的第一个问题,迭代器不一定对元素进行快照。这取决于实际的集合实现。一些集合(比如 CopyOnWriteArrayList
)确实有快照语义,所以如果集合被修改,修改对迭代器是不可见的。在这种情况下,迭代器报告的元素数将匹配 size()
,因此不需要重新分配数组。
其他集合实现对于在迭代期间修改集合时会发生什么有不同的策略。有些是 fail-fast,这意味着他们会抛出 ConcurrentModificationException
。其他的弱一致,这意味着迭代器可能会或可能不会看到修改。
这适用于你的第二个问题。如果集合大小在迭代过程中发生变化,并且该集合的迭代器支持此功能(即,它不是 fail-fast),则此处的代码将处理迭代器中出现的元素数量与 [=10 最初报告的数量不同=].
一个可能发生这种情况的例子是 ConcurrentSkipListSet
。这个class的迭代器是弱一致的,它继承了AbstractCollection
的toArray()
方法。因此,当 toArray()
迭代集合以将元素收集到目标数组中时,另一个线程修改集合(可能会更改其大小)是完全合法的。这显然会导致迭代器报告与 size()
返回的初始值不同的元素数,这将导致执行 toArray()
中的数组重新分配代码。