作者回复: 陆离同学一直保持非常稳定的发挥,答案非常准确!
作者回复: for(:)循环[这里指的不是for(;;)]是一个语法糖,这里会被解释为迭代器,在使用迭代器遍历时,ArrayList内部创建了一个内部迭代器iterator,在使用next()方法来取下一个元素时,会使用ArrayList里保存的一个用来记录List修改次数的变量modCount,与iterator保存了一个expectedModCount来表示期望的修改次数进行比较,如果不相等则会抛出异常;
而在在foreach循环中调用list中的remove()方法,会走到fastRemove()方法,该方法不是iterator中的方法,而是ArrayList中的方法,在该方法只做了modCount++,而没有同步到expectedModCount。
当再次遍历时,会先调用内部类iteator中的hasNext(),再调用next(),在调用next()方法时,会对modCount和expectedModCount进行比较,此时两者不一致,就抛出了ConcurrentModificationException异常。
所以关键是用ArrayList的remove还是iterator中的remove。
作者回复: 关键在用谁的remove方法。
作者回复: 👍
作者回复: 这个是随机的,因为分配的内存地址不是连续的。
作者回复: 因为for循环需要遍历链表,每循环一次就需要遍历一次指定节点前的数据,源码如下:
// 获取双向链表中指定位置的节点
private Entry<E> entry(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+size);
Entry<E> e = header;
// 获取index处的节点。
// 若index < 双向链表长度的1/2,则从前先后查找;
// 否则,从后向前查找。
if (index < (size >> 1)) {
for (int i = 0; i <= index; i++)
e = e.next;
} else {
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}
而iterator在第一次拿到一个数据后,之后的循环中会使用Iterator中的next()方法采用的是顺序访问。
作者回复: 没啥区别的,可以实际操作试试
作者回复: 这里指的是不需要通过遍历寻址,可以通过index直接访问到内存地址。
作者回复: 是的,是因为连续内存。在代码中,程序是不知道底层开辟的内存情况,所以需要一个类似序列化的接口标志,这个接口仅仅是一个标志,并不是实现。
作者回复: 对的,使用普通循环也需要注意。
作者回复: HashMap有负载因子是既要考虑数组太短,因哈希冲突导致链表过长而导致查询性能下降,也考虑了数组过长,新增数据时性能下降。这个负载因子是综合了数组和链表两者的长度,不能太大也不能太小。而ArrayList不需要这种考虑。
作者回复: 对的!
作者回复: 厉害了,感谢建议。这里很多同学没有了解过JMH测试框架,所以没有使用。
作者回复: 是的,不要使用迭代器循环时用ArrayList的remove方法,具体分析可以看留言区。
作者回复: 赞