使用流来查找列表列表中的对象

时间:2015-11-21 02:26:34

标签: java java-8 java-stream

我正在尝试编写一个方法,在列表列表中查找对象的索引并利用并行性。这是我的代码。

// returns [i, j] where lists.get(i).get(j) equals o, or null if o is not present.
public static int[] indices(List<? extends List<?>> lists, Object o) {
    return IntStream.range(0, lists.size())
                    .boxed()
                    .flatMap(i -> IntStream.range(0, lists.get(i).size()).mapToObj(j -> new int[]{i, j}))
                    .parallel()
                    .filter(a -> {
                        System.out.println(Arrays.toString(a));     // For testing only
                        return Objects.equals(o, lists.get(a[0]).get(a[1]));
                    })
                    .findAny()
                    .orElse(null);
}

当我运行以下代码时

List<List<String>> lists = Arrays.asList(
        Arrays.asList("A", "B", "C"),
        Arrays.asList("D", "E", "F", "G"),
        Arrays.asList("H", "I"),
        Collections.nCopies(5, "J")
);
System.out.println("Indices are " + Arrays.toString(indices(lists, "J")));

输出类似于

[0, 0]
[0, 1]
[0, 2]
[3, 0]
[3, 1]
[3, 2]
[3, 3]
[2, 0]
[3, 4]
[1, 0]
[1, 1]
[2, 1]
[1, 2]
[1, 3]
Indices are [3, 0]

换句话说,即使找到对象,搜索仍会继续。是不是findAny应该是短路操作?我错过了什么?在迭代列表列表或锯齿状数组时,利用并行性的最佳方法是什么?

修改

按照@ Sotirios的回答,我得到了

的输出
Thread[ForkJoinPool.commonPool-worker-3,5,main] [3, 0]
Thread[main,5,main] [2, 0]
Thread[main,5,main] [2, 1]
Thread[ForkJoinPool.commonPool-worker-1,5,main] [1, 0]
Thread[ForkJoinPool.commonPool-worker-1,5,main] [1, 1]
Thread[ForkJoinPool.commonPool-worker-1,5,main] [1, 2]
Thread[ForkJoinPool.commonPool-worker-1,5,main] [1, 3]
Thread[main,5,main] [0, 0]
Thread[main,5,main] [0, 1]
Thread[ForkJoinPool.commonPool-worker-3,5,main] [3, 1]
Thread[main,5,main] [0, 2]
Thread[ForkJoinPool.commonPool-worker-3,5,main] [3, 2]
Thread[ForkJoinPool.commonPool-worker-3,5,main] [3, 3]
Thread[ForkJoinPool.commonPool-worker-3,5,main] [3, 4]
Indices are [3, 0]

请注意

Thread[ForkJoinPool.commonPool-worker-3,5,main]

即使找到答案后仍继续搜索。

3 个答案:

答案 0 :(得分:7)

短路操作不能保证只产生尽可能少的元素来产生结果。他们可能这样做,但不是必需的。

flatMap的当前实现是这样的,它总是将子流的整个内容推向下游。因此,即使您的流不是并行的,您也可以看到流过流的元素多于满足findAny所需的元素。

答案 1 :(得分:2)

至于“为何以这种方式实施”。问题在于Stream API实现。 flatMap正文通常会创建一个包含一些中间操作的流(如.flatMap(list -> list.stream().map(...).filter(...)))。可以在flatMap实施stream.spliterator()内使用并多次调用tryAdvance,直到请求取消为止。但是,当流包含中间操作时,spliterator()调用会返回一些人工分裂器(如果不是,则只返回原始流分裂器)。这种人工分裂器没有非常高效的tryAdvance()实现,因此与使用整个flatMapped流相比,使用此实现可能被认为是更差的性能缺陷。在许多情况下,你将flatMap映射到一些短流,所以在这里你可以通过当前的实现获得性能提升。

答案 2 :(得分:1)

并不是它继续,它已经调度了各种线程来尝试找到结果,并且会等到那些完成后再返回结果。

换句话说,findAny终端操作将提交&#34;搜索&#34;任务到多个线程。这些任务只是应用filter Predicate并在某些内容返回true时返回。据推测,findAny等待其中一个返回值。它没有办法真正取消它已经提交的任何东西,似乎这个实现将阻塞,直到整个批处理返回。它只能停止提交任何未来的批次。

您可以通过记录当前线程来验证这一点:

System.out.println(Thread.currentThread() + " " + Arrays.toString(a)); // For testing only