为什么在java中这样实现了这样的实现?

时间:2016-01-07 15:02:58

标签: java collections 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);
}

如您所见,实施起来并不容易理解,我的问题是:

  1. 在迭代期间,当集合的元素发生变化(大小未更改)时,我会得到什么?我想迭代器可能是某种快照。
  2. 当收藏品的尺寸发生变化时,我会得到什么?我想知道它是否能正常工作。

4 个答案:

答案 0 :(得分:3)

  

如您所见,实施并不那么容易理解,我的问题是:

     
      
  1. 在迭代期间,当集合的元素发生变化(大小未更改)时,我会得到什么?我猜迭代器可能是某种快照。
  2.   
  3. 当收藏品的尺寸发生变化时,我会得到什么?我想知道它是否能正常工作。
  4.   

实现是这样的,因为它旨在处理迭代器返回的元素数量不同于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中这样实现了这样的实现?

因为此实现假定集合的大小可以随时更改,因为迭代器可能不会抛出任何异常。 因此,此方法检查迭代器是否可以提供比初始估计大小更多或更少的元素。