当前位置: 首页 > 知识库问答 >
问题:

修改的基础集合上的Spliterator

蔡明贤
2023-03-14

我知道这在生产中绝不应该发生,但我正在试图理解一些关于spliterator的错综复杂的细节,并撞见了下面这个“谜题”(至少对我来说是个谜题):

(代码段%1)

List<Integer> list = new ArrayList<>() {{ add(1); add(2); add(3); add(4); }};
Spliterator<Integer> spl1 = list.spliterator();
list.add(5);
list.add(6);
Spliterator<Integer> spl2 = s1.trySplit();
s1.forEachRemaining(System.out::print);
s2.forEachRemaining(System.out::print);

这段代码按预期打印456123(cough我已经预期了一个ConcurrentModificationException,但我理解行为cough),即,它在列表上创建了一个拆分器,当列表有6个元素时,该拆分器将被拆分。

我不明白的是以下几点:

(片段2)

List<Integer> list = new ArrayList<>() {{ add(1); add(2); add(3); add(4); }};
Spliterator<Integer> spl1 = list.spliterator();
Spliterator<Integer> spl2 = s1.trySplit();
list.add(5);
list.add(6);
s1.forEachRemaining(System.out::print);
s2.forEachRemaining(System.out::print);

我预计这段代码会失败,但它确实失败了,因为在S1.ForeachReflay的行上有一个ConcurrentModificationException,但是它也会将34打印到输出中。如果将其更改为system.err::println,则会看到值34分别按此顺序放在异常之前的printstream上。

现在疯狂的部分:

(片段3)

List<Integer> list = new ArrayList<>() {{ add(1); add(2); add(3); add(4); }};
Spliterator<Integer> spl1 = list.spliterator();
Spliterator<Integer> spl2 = s1.trySplit();
list.add(5);
list.add(6);
s2.forEachRemaining(System.out::print);
s1.forEachRemaining(System.out::print);

注意,片段2和3之间唯一的变化是我们访问S1S2的顺序。代码段3仍然失败,出现ConcurrentModificationException,但打印的值为12。这是因为异常现在发生在s2.foreachlefting的行上!

如果我没有理解错的话,发生的事情是:

  • 拆分器已初始化
  • 拆分完成
  • 迭代发生
    • 在迭代过程中,在完成最后一次拆分之后,可以观察到对基础集合进行了修改

    这是不是意味着,劈叉者也是“懒人”,就像溪流一样?然而,当实验多个分裂时,

    (片段4)

    List<Integer> list = new ArrayList<>() {{ add(1); add(2); add(3); add(4); add(5); add(6); add(7); add(8); add(9); add(10); }};
    Spliterator<Integer> s1 = list.spliterator();
    Spliterator<Integer> s2 = s1.trySplit();
    list.add(11);
    list.add(12);
    Spliterator<Integer> s3 = s2.trySplit();
    s1.forEachRemaining(s -> System.err.println("1 " + s));
    s2.forEachRemaining(s -> System.err.println("2 " + s));
    s3.forEachRemaining(s -> System.err.println("3 " + s));
    

    然后,应该对s1进行无问题计算,并在处理s2时抛出异常,但在处理s1时已经抛出异常!

    感谢任何帮助或指示。

    详细信息:我在Eclipse2019-06(4.12.0)中的Windows上运行AdoptOpenJDK 11.0.4+11(64位)的代码片段(如果重要的话)。

共有1个答案

厉高逸
2023-03-14

与您的第一个代码段(较小的错误已更正)

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
Spliterator<Integer> spl1 = list.spliterator();
list.add(5);
list.add(6);
Spliterator<Integer> spl2 = spl1.trySplit();
spl1.forEachRemaining(System.out::print);
spl2.forEachRemaining(System.out::print);

您正在观察支持更改的文档化行为:

不报告immutableconcurrent的拆分器应该有一个文档化的策略:当拆分器绑定到元素源时;以及结合后检测到的元素源的结构干涉的检测。后期绑定拆分器在第一次遍历、第一次拆分或第一次查询估计大小时绑定到元素源,而不是在创建拆分器时绑定到元素源。不是后期绑定的拆分器在构造或首次调用任何方法时绑定到元素的源。在绑定之前对源所做的修改会在遍历拆分器时反映出来。绑定后,如果检测到结构干扰,则拆分器应尽最大努力抛出ConcurrentModificationException。这样做的分裂器称为fail-fast。拆分器的批量遍历方法(foreachlefting())可以优化遍历,并在遍历所有元素后检查结构干扰,而不是按元素检查并立即失败。

所以在这里,我们看到一个后期绑定的分裂器在起作用。在任何遍历之前所做的更改都会在遍历期间反映出来。这是类似行为的基础,该行为建立在拆分器上:

除非流源是并发的,否则在执行流管道期间修改流的数据源可能会导致异常、错误的答案或不符合要求的行为。对于行为良好的流源,可以在终端操作开始之前修改源,这些修改将反映在覆盖的元素中。例如,请考虑以下代码:

List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
l.add("three");
String s = sl.collect(joining(" "));

首先创建一个由两个字符串组成的列表:“one”;和“二”。然后从该列表创建一个流。接下来,通过添加第三个字符串来修改列表:“three”。最后,流的元素被收集并连接在一起。由于列表是在终端collection操作开始之前修改的,因此结果将是一个“One two Three”字符串。

对于您的其他片段,这句话适用

一个后期绑定拆分器绑定到元素源在第一次遍历,第一次拆分,或第一次查询估计的大小…

因此,在第一次(成功的)trysplit调用时绑定拆分器,然后将元素添加到源列表将使其无效,所有拆分器都从其拆分。

所以和

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
Spliterator<Integer> spl1 = list.spliterator();
Spliterator<Integer> spl2 = spl1.trySplit();
list.add(5);
list.add(6);
spl1.forEachRemaining(System.out::print);
spl2.forEachRemaining(System.out::print);

您将获得描述为优化的Fail-fast的行为:

拆分器的批量遍历方法(foreachlefting())可以优化遍历,并在遍历所有元素后检查结构干扰,而不是按元素检查并立即失败。

因此,您可以看到在其上调用ForeachRefling(System.out::Print)的第一个拆分器的元素,后面是ConcurrentModificationException。更改最后两条语句的顺序只是更改了在抛出异常之前打印哪些元素,与描述完美匹配。

最后一个片段只演示了trysplit不会自行执行检查。它成功地拆分了一个已经失效的拆分器,导致另一个无效的拆分器。所有生成的分裂器的行为保持不变,第一个分裂器将检测到干扰,但出于性能考虑,只有在遍历之后才检测到。

我们可以验证所有创建的spliterator的行为都是相同的:

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
Spliterator<Integer> s1 = list.spliterator();
Spliterator<Integer> s2 = s1.trySplit();
list.add(11);
list.add(12);
Spliterator<Integer> s3 = s2.trySplit();
try {            
    s1.forEachRemaining(s -> System.out.println("1 " + s));
} catch(Exception ex) {
    System.out.println("1 "+ex);
}
try {            
    s2.forEachRemaining(s -> System.out.println("2 " + s));
} catch(Exception ex) {
    System.out.println("2 "+ex);
}
try {            
    s3.forEachRemaining(s -> System.out.println("3 " + s));
} catch(Exception ex) {
    System.out.println("3 "+ex);
}
1 6
1 7
1 8
1 9
1 10
1 java.util.ConcurrentModificationException
2 3
2 4
2 5
2 java.util.ConcurrentModificationException
3 1
3 2
3 java.util.ConcurrentModificationException
 类似资料:
  • 简介 多数主流编程语言都提供了若干种复杂数据结构,而在ES6以前,js只有数组和对象两种 ES6为了弥补这一方面的不足,引入了四种新的数据结构 它们分别是:映射(Map)、集合(Set)、弱集合(WeakSet)和弱映射(WeakMap) 正文 Set类似数组,但是成员的值都是唯一的,没有重复的值 let set = new Set([1, 2, 3, 3]) console.log(set) /

  • 问题内容: 我在下面有此代码,并且通过执行以下行来获取ConcurrentModificationException: 代码: 堆栈是: 恰好在foreach行上: 我不明白为什么会发生此错误,因为我没有修改列表。 问题答案: 要知道,是 不是抄袭 收集数据,但只有包装原始集合在一个特殊的包装。因此,如果您修改原始集合,则会出现此错误。 如果要创建真正独立的不可修改的集合实例:

  • 接口说明 校验要上传的文件的每一个文件分片 如需调用,请访问 开发者文档 来查看详细的接口使用说明 该接口仅开放给已获取SDK的开发者 API地址 POST /api/server/1.0.0/update 是否需要登录 是 请求字段说明 参数 类型 请求类型 是否必须 说明 guid string form 是 素材id title string form 否 素材标题 position str

  • Java集合框架提供了数据持有对象的方式,提供了对数据集合的操作。Java集合框架位于java.util包下,主要有三个大类:Collection、Map接口以及对集合进行操作的工具类。 Collection ArrayList:线程不同步。默认初始容量为10,当数组大小不足时增长率为当前长度的50%。 Vector:线程同步。默认初始容量为10,当数组大小不足时增长率为当前长度的100%。它的同

  • 我正在从一个可观察的列表中设置一个ListView,该列表有另一个集合的输入(在本例中是一个链表)。因此,我在这个答案中找到了如何使列表视图的项从中移除(我不太确定它们是否也从ObservableList中移除),那么还有什么可能的方法可以在两个集合(即ObservableList和原始集合)中进行修改呢? 下面是一段代码:

  • 尝试添加到集合时出现“Collection was modified”异常 问题出在“添加”方法上。如果我注释掉该部分,则不会引发异常。重要的是要注意,我已经尝试将foreach重写为for循环并添加“ToList()”以形成。链接。在所有情况下都会引发相同的异常。我在站点的其他部分使用这种完全相同的模式没有问题,这就是为什么这如此令人沮丧。这也适用于“创建”。问题只影响“编辑”操作。 其他相关代