簡體   English   中英

Thread.sleep 在 Stream 處理中沒有影響嗎?

[英]Does Thread.sleep have no effect in Stream processing?

以下程序來自 Jeanne Boyarsky 和 Scott Selikoff 的 OCP 學習指南:

import java.util.*;

class WhaleDataCalculator {
    public int processRecord(int input) {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            // Handle interrupted exception
        }
        return input + 1;
    }

    public void processAllData(List<Integer> data) {
        data.stream().map(a -> processRecord(a)).count();
    }

    public static void main(String[] args) {
        WhaleDataCalculator calculator = new WhaleDataCalculator();
        // Define the data
        List<Integer> data = new ArrayList<Integer>();
        for (int i = 0; i < 4000; i++)
            data.add(i);
        // Process the data
        long start = System.currentTimeMillis();
        calculator.processAllData(data);
        double time = (System.currentTimeMillis() - start) / 1000.0;
        // Report results
        System.out.println("\nTasks completed in: " + time + " seconds");
    }
}

作者聲稱

假設有 4,000 條記錄,每條記錄需要 10 毫秒來處理,通過使用串行 stream(),結果將需要大約 40 秒才能完成此任務。

但是,當我在我的系統中運行它時,每次運行需要 0.006 秒到 0.009 秒。

差異在哪里?

這是因為使用了count ,它在后來的 Java 版本中執行了一個技巧。

由於您只對元素的數量感興趣,因此count將嘗試直接從源中獲取大小,並將跳過大多數其他操作。 這是可能的,因為您只是在做一個map而不是,例如,一個filter ,所以元素的數量不會改變。

如果添加peek(System.out::println) ,您也不會看到 output。

如果您調用forEach而不是count ,運行代碼可能需要 40 秒。

.map(a -> processRecord(a))的調用根本沒有運行,原因是你運行這個程序的 JDK 版本超過 1.8。

為了便於理解,我們舉這個例子:

long number = Stream.of("x", "x", "x").map(e -> {
            System.out.println("Hello");
            return e;
        }).count();
        
        System.out.println(number);

嘗試使用 JDK 1.8 運行它,然后使用 JDK 11 運行它。

在java 8中, count()作為一個終端操作,所有的中間操作(這里是map方法)都會被執行,map操作會被執行並打印hello消息。 你會得到這個 output:

Hello
Hello
Hello
3

在大於1.8的Java版本中,這里以11為例, Java可以直接確定stream的元素個數,如果沒有中間操作可以改變stream的元素個數(例子:filter()),沒有會執行中間方法,只執行count方法,所以你不會看到任何hello消息,但是會計算這個stream的元素個數,你可以使用它。 您的 output 將是這樣的:

3

如果你想在 Java 大於 1.8 的版本中看到 hello 消息,你應該在你的 stream 管道中添加一個中間操作,它可以改變 stream 的元素數量,讓我們將過濾器方法添加到管道中並查看 output on java 11:

long number = Stream.of("x", "x", "x").map(e -> {
            System.out.println("Hello");
            return e;
        }).filter(element-> element.equals("x")).count();
        
        System.out.println(number);

這里是 output:

Hello
Hello
Hello
3

由於 Java 9 操作count()已經優化,因此如果在 stream 的初始化期間(當管道的各個階段被鏈接時)結果表明沒有可以更改 stream 源中元素數量的操作允許評估它包含的元素數量,然后count()不會觸發管道的執行,而是詢問源“你有多少這些人?” 並立即返回值。

這是文檔中的引述:

API 備注:

如果實現能夠直接從 stream 源計算計數,則它可以選擇不執行 stream 管道(順序或並行)。 在這種情況下,不會遍歷任何源元素,也不會評估任何中間操作。 具有副作用的行為參數,除了調試等無害情況外,強烈建議不要使用,可能會受到影響。例如,考慮以下 stream:

 List<String> l = Arrays.asList("A", "B", "C", "D"); long count = l.stream().peek(System.out::println).count();

stream 源List涵蓋的元素數量是已知的,中間操作peek不會從 stream 中注入或移除元素(對於flatMapfilter操作可能就是這種情況)。 因此countList的大小,不需要執行管道,作為副作用,打印出列表元素。

順便說一下,除了這個測試背后的技巧之外,這個案例不需要使用 Stream API。因為count()返回的值被忽略了,所有需要做的就是對每個元素產生副作用列表,然后可以使用Iterable.forEach()代替:

public void processAllData(List<Integer> data) {
    data.forEach(a -> processRecord(a));
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM