java - 基于索引的迭代期间没有 ConcurrentModificationException
问题描述
我有以下代码:
public static void main(String[] args) {
List<String> input = new ArrayList<>();
List<String> output = new ArrayList<>();
for(int i=0; i< 1000 ;i++){
input.add(i+"");
}
for(int i=0 ; i<input.size(); i++){
String value = input.get(i);
if(Integer.parseInt(value) % 2 == 0){
output.add(value);
input.remove(value);
}
}
input.stream().forEach(System.out::println);
System.out.println("--------------------------------------");
output.stream().forEach(System.out::println);
}
我预计它会抛出ConcurrentModificationException
,但它工作正常。有人能解释一下原因吗?
解决方案
原因是您在技术上没有迭代列表。相反,您使用递增索引随机访问列表,并删除一些值。如果您更改为这样的代码来迭代列表,它将抛出ConcurrentModificationException
public static void main(String[] args) {
List<String> input = new ArrayList<>();
List<String> output = new ArrayList<>();
for(int i=0; i< 1000 ;i++){
input.add(i+"");
}
for (String value : input) {
if(Integer.parseInt(value) % 2 == 0){
output.add(value);
input.remove(value);
}
}
input.stream().forEach(System.out::println);
System.out.println("--------------------------------------");
output.stream().forEach(System.out::println);
}
跟进为什么与迭代器相比,这可能不是首选方式。一个原因是性能。这是一些使用 JMH 进行测试的基准代码。
package bench;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.SECONDS;
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 1, time = 3, timeUnit = SECONDS)
@Measurement(iterations = 3, time = 2, timeUnit = SECONDS)
public class JmhBenchmark {
private List<String> input;
@Param({"100", "1000", "10000"})
public int length;
@Setup(Level.Invocation)
public void createInputList() {
input = new ArrayList<>();
for (int i = 0; i < length; i++) {
input.add(i + "");
}
}
@Benchmark
public void iterateWithVariable() {
for (int i = 0; i < input.size(); i++) {
String value = input.get(i);
if (Integer.parseInt(value) % 2 == 0) {
input.remove(value);
}
}
}
@Benchmark
public void iterateWithIterator() {
final Iterator<String> iterator = input.iterator();
while (iterator.hasNext()) {
String value = iterator.next();
if (Integer.parseInt(value) % 2 == 0) {
iterator.remove();
}
}
}
}
我系统上的基准测试结果是
Benchmark (length) Mode Cnt Score Error Units
JmhBenchmark.iterateWithIterator 100 avgt 15 0.002 ± 0.001 ms/op
JmhBenchmark.iterateWithIterator 1000 avgt 15 0.033 ± 0.001 ms/op
JmhBenchmark.iterateWithIterator 10000 avgt 15 1.670 ± 0.017 ms/op
JmhBenchmark.iterateWithVariable 100 avgt 15 0.005 ± 0.001 ms/op
JmhBenchmark.iterateWithVariable 1000 avgt 15 0.350 ± 0.014 ms/op
JmhBenchmark.iterateWithVariable 10000 avgt 15 33.591 ± 0.455 ms/op
所以我们可以看到使用迭代器从列表中删除一些项目比这个问题提出的方法快很多(> 20 倍)。这是有道理的,您需要在列表中执行随机查找,然后确定是否需要将其删除,然后再进行一次查找以查找并删除它。
推荐阅读
- azure-ad-b2c - 用于创建 AzureADB2C 的 Azure Fluent Api
- r - 有没有办法在不使下游目标无效的情况下将 file_in 输入的绝对路径更改为 drake?
- javascript - Laravel newly added javascript files give an error net::ERR_ABORTED 404 (Not Found)
- c# - 使用 NLOG 时无法解析服务时缺少详细信息
- c++ - (C++) 异常未处理 - 无效向量
下标 - node.js - 如何使用 express 将我的 mongoDB 查询显示为 json?
- r - 如何使用 barplot() 手动分配沿 x 轴的条形位置?
- c# - 获取计划返回 401 - 未经授权
- php - 如何在 PHP 中分配具有多个返回的变量?
- java - 使用 buffreader 进行 JAva 编程