簡體   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