Java 什么是堆写入流量?为什么在ArrayList中需要它?

Java 什么是堆写入流量?为什么在ArrayList中需要它?,java,Java,我只是想知道这意味着什么,为什么在ArrayList实现中需要它 ArrayList实现的代码片段,请参见带有注释的行 @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size;

我只是想知道这意味着什么,为什么在ArrayList实现中需要它

ArrayList
实现的代码片段,请参见带有注释的行

@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
    Objects.requireNonNull(consumer);
    final int size = ArrayList.this.size;
    int i = cursor;
    if (i >= size) {
        return;
    }
    final Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length) {
        throw new ConcurrentModificationException();
    }
    while (i != size && modCount == expectedModCount) {
        consumer.accept((E) elementData[i++]);
    }
    // update once at end of iteration to reduce heap write traffic
    cursor = i;
    lastRet = i - 1;
    checkForComodification();
}
@覆盖
@抑制警告(“未选中”)

公共空间无效(消费者可能是作者想使用局部变量
i
,因为它可能在启动时被堆栈分配。与
cursor
不同,
i
变量由于
i++
语句在
while
循环中被多次更改。在堆栈上增加它应该更便宜kip所有Java内存模型含义。
Iterator.cursor
是一个成员字段,它可能总是在堆上,尤其是
Iterator
对象在用户代码中传递。

通常
cursor
变量指向
Iterator
返回的下一个元素uld需要每次更新
光标
变量,使其始终指向正确的元素

但是,
forEachRemaining
方法本身就完成了迭代。它并不意味着要暂停。因此,在方法完成之前,您可以忽略更新游标变量。当方法迭代时,
游标将指向错误的元素。但由于您不能暂停该方法,因此不会产生任何差异法国

这样可以减少对
游标的分配量,从而减少堆流量

while (i != size && modCount == expectedModCount) {
    consumer.accept((E) elementData[i++]);
    // Update cursor while iterating
    cursor = i;
}
或者直接使用光标而不是附加的
i

while (cursor != size && modCount == expectedModCount) {
    consumer.accept((E) elementData[cursor++]);
}

但是,您可以使用成员变量而不是局部变量
i
。使用
i
更便宜,有关详细信息,请参阅@kdowbecki的答案。

如果忽略所有保护条件,
next()
执行以下操作:

public E next() {
    Object[] elementData = ArrayList.this.elementData;

    int i = cursor;
    cursor = i + 1;
    lastRet = i;
    return (E) elementData[i];
}
forEachRemaining()
基本上会继续调用
next()
并在每个元素上调用使用者,因此如果我们这样做,内联
next()
逻辑,我们会得到:

public void forEachRemaining(Consumer<? super E> consumer) {
    final int size = ArrayList.this.size;
    final Object[] elementData = ArrayList.this.elementData;

    int i = cursor;
    while (i != size) { // same as hasNext()
        // begin: consumer.accept(next())
        cursor = i + 1;
        lastRet = i;
        consumer.accept((E) elementData[i]);
        // end: consumer.accept(next())
        i++;
    }
}
其效果是,在迭代过程中,仅更新堆栈变量
i
,而两个堆值被单独保留


一旦JIT启动,如果
accept()
调用是内联的,那么堆栈变量
i
甚至可能被删除,成为一个寄存器值,从而大大减少了“slow”的更新次数内存。

感谢您提供的答案。为了更好地解释,我添加了内部Java内存模型,如下图所示。正如@kdowbecki、@Zabuza和@Andreas所指出的,在本地执行时使用
线程堆栈
内存,然后在每次迭代时使用
内存是有效的。它可能会失败r这一类

在操作系统中检查(JVM是一个进程)也很有趣

public void forEachRemaining(Consumer<? super E> consumer) {
    final int size = ArrayList.this.size;
    final Object[] elementData = ArrayList.this.elementData;

    int i = cursor;
    while (i != size) {
        consumer.accept((E) elementData[i++]);
    }
    cursor = i;
    lastRet = i - 1;
}