繁体   English   中英

将对象从流同时添加到两个不同的列表

[英]Add objects from stream to two different lists simultaneously

如何同时将对象从一个流添加到两个不同的列表

目前我在做

body.getSurroundings().parallelStream()
                .filter(o -> o.getClass().equals(ResourcePoint.class))
                .map(o -> (ResourcePoint)o)
                .filter(o -> !resourceMemory.contains(o))
                .forEach(resourceMemory::add);

将我的流中的对象添加到链表“resourceMemory”中,但我也想同时将相同的对象添加到另一个列表,但我找不到它的语法。 是否可能或者我是否需要为每个列表提供此代码的两个副本?

在尝试扩展代码之前,首先应该了解一些基本错误。

首先, forEach不保证元素处理的特定顺序,因此它可能是添加到List的错误工具,即使对于顺序流,但是,使用并行流添加到集合中是完全错误的LinkedList这不是线程安全的,因为动作将同时进行。

但即使resourceMemory是一个线程安全集合,您的代码仍然被破坏,因为您的filter条件和终端操作之间存在干扰。 .filter(o -> !resourceMemory.contains(o))查询您在终端操作中修改的相同列表,并且应该很难理解即使使用线程安全的集合,它也会如何制动:

两个或多个线程可以处理过滤器并发现该元素未包含在列表中,然后所有这些都将添加该元素,这与您没有重复的明显意图相矛盾。

您可以使用forEachOrdered ,它将按顺序执行操作,而不是同时执行:

body.getSurroundings().parallelStream()
    .filter(o -> o instanceof ResourcePoint)
    .map(o -> (ResourcePoint)o)
    .forEachOrdered(o -> {// not recommended, just for explanation
        if(!resourceMemory.contains(o))
            resourceMemory.add(o);
    });

这将是有效的,并且很明显如何添加到该操作中的另一个列表,但它远离推荐的编码风格。 此外,此终端操作与所有处理线程同步的事实将破坏并行处理的任何潜在好处,尤其是当此流管道的最昂贵操作调用contains在将必须发生单线程的LinkedList上时。

将流元素收集到列表中的正确方法是通过,如顾名思义, collect

List<ResourcePoint> resourceMemory
    =body.getSurroundings().parallelStream()
        .filter(o -> o instanceof ResourcePoint)
        .map(o -> (ResourcePoint)o)
        .distinct()                    // no duplicates
        .collect(Collectors.toList()); // collect into a list

这不会返回LinkedList ,但您应该仔细重新考虑是否确实需要LinkedList 在99%的情况下,你没有。 如果您确实需要LinkedList ,则可以使用Collectors.toCollection(LinkedList::new)替换Collectors.toList() Collectors.toCollection(LinkedList::new)

现在,如果你真的必须添加到你的控件之外创建的现有列表(可能已经包含元素),你应该考虑上面提到的事实,你必须确保单线程访问非线程安全列表,所以从并行流中完成它没有任何好处。 在大多数情况下,让流独立于该列表工作并在之后的单个线程步骤中添加结果会更有效:

Set<ResourcePoint> newElements=
    body.getSurroundings().parallelStream()
        .filter(o -> o instanceof ResourcePoint)
        .map(o -> (ResourcePoint)o)
        .collect(Collectors.toCollection(LinkedHashSet::new));
newElements.removeAll(resourceMemory);
resourceMemory.addAll(newElements);

在这里,我们收集到LinkedHashSet ,它意味着维护遭遇顺序并在新元素中排序重复,然后在新元素上使用removeAll来删除目标列表的现有元素(这里我们受益于临时的哈希集性质)最后,新元素被添加到目标列表中,正如所解释的那样,对于非线程安全的目标集合,无论如何必须发生单线程。

使用此解决方案将newElements添加到另一个目标集合很容易,比在流处理期间编写自定义收集器以生成两个列表要容易得多。 但请注意,上面写的流操作太过于难以承担并行处理的任何好处。 您需要非常多的元素来补偿初始的多线程开销。 甚至有可能没有它能够得到回报的数字。

代替

.forEach(resourceMemory::add)

你可以调用

.forEach(o -> {
   resourceMemory.add(o);
   otherResource.add(o);
 })

或者将add操作放在一个单独的方法中,以便提供方法引用

.forEach(this::add)

void add(ResourcePoint p) {
   resourceMemory.add(o);
   otherResource.add(o);
}

但请记住,当您使用并行流时,每次运行的插入顺序可能不同。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM