Java8新特性之Streams和Parallel Streams

一、Streams(流)

java.util.Stream 表示能應用在一組元素上一次執行的操作序列。Stream 操作分爲中間操作或者最終操作兩種,最終操作返回一特定類型的計算結果,而中間操作返回Stream本身,這樣你就可以將多個操作依次串起來。Stream 的創建需要指定一個數據源,比如java.util.Collection 的子類,List 或者 Set, Map 不支持。Stream 的操作可以串行執行或者並行執行。

首先看看Stream是怎麼用,首先創建實例代碼的用到的數據stringList:

public class StreamTest {
	private List<String> stringList = new ArrayList<>();
	public List<String> list() {
    	stringList.add("ddd2");
    	stringList.add("aaa2");
      	stringList.add("bbb1");
     	stringList.add("aaa1");
     	stringList.add("bbb3");
        stringList.add("ccc");
       	stringList.add("bbb2");
        stringList.add("ddd1");
        return stringList;
    }
}

Java 8擴展了集合類,可以通過 Collection.stream() 或者 Collection.parallelStream() 來創建一個Stream。下面幾節將詳細解釋常用的Stream操作:

1、Filter(過濾)

過濾通過一個predicate接口來過濾並只保留符合條件的元素,該操作屬於中間操作,所以我們可以在過濾後的結果來應用其他Stream操作(比如forEach)。forEach需要一個函數來對過濾後的元素依次執行。forEach是一個最終操作,所以我們不能在forEach之後來執行其他Stream操作。

        //創建streamTest對象
        StreamTest streamTest = new StreamTest();
        //給list賦值
        List<String> list = streamTest.list();
        System.out.println(list.parallelStream());
        //測試filter過濾和foreach
        list
                .stream()
                .filter(s -> s.startsWith("a"))
                .forEach(System.out::println);
aaa2
aaa1

forEach 是爲 Lambda 而設計的,保持了最緊湊的風格。而且 Lambda 表達式本身是可以重用的,非常方便。

2、Sorted(排序)

排序是一個 中間操作,返回的是排序好後的 Stream。如果你不指定一個自定義的 Comparator 則會使用默認排序。

        //測試sort排序,sort函數不帶參數默認排序也是從小到大
        list.
                stream().
                sorted((s1, s2) -> s1.compareTo(s2)).
                filter(s -> s.startsWith("a")).
                forEach(System.out::println);
aaa1
aaa2

需要注意的是,排序只創建了一個排列好後的Stream,而不會影響原有的數據源,排序之後原數據stringCollection是不會被修改的:

    System.out.println(list);// [ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1]

3、Map(映射)

中間操作 map 會將元素根據指定的 Function 接口來依次將元素轉成另外的對象。

下面的示例展示了將字符串轉換爲大寫字符串。你也可以通過map來將對象轉換成其他類型,map返回的Stream類型是根據你map傳遞進去的函數的返回值決定的。

        //測試map,將元素根據指定的Function接口來依次轉換成另外的對象
        list.
                stream().
                map(String::toUpperCase).
                sorted((a, b) -> b.compareTo(a)).
                forEach(System.out::println);
DDD2
DDD1
CCC
BBB3
BBB2
BBB1
AAA2
AAA1

4、Match(匹配)

Stream提供了多種匹配操作,允許檢測指定的Predicate是否匹配整個Stream。所有的匹配操作都是 最終操作 ,並返回一個 boolean 類型的值。

        boolean anyStartWithA = list.stream().anyMatch(s -> s.startsWith("a"));
        System.out.println(anyStartWithA);

        boolean allStartWithA = list.stream().allMatch(s -> s.startsWith("a"));
        System.out.println(allStartWithA);

        boolean noneStartWithZ = list.stream().noneMatch(s -> s.startsWith("z"));
        System.out.println(noneStartWithZ);
true
false
true

5、Count(計數)

計數是一個 最終操作,返回Stream中元素的個數,返回值類型是 long

		long startWithB = list.stream().filter(s -> s.startsWith("b")).count();
    	System.out.println(startWithB);
3

6、Reduce(規約)

這是一個 最終操作 ,允許通過指定的函數來將stream中的多個元素規約爲一個元素,規約後的結果是通過Optional 接口表示的:

	Optional<String> reduce = list.stream().sorted().reduce((a, b) -> a + "#" + b);
	System.out.println(reduce);
Optional[aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2]

譯者注: 這個方法的主要作用是把 Stream 元素組合起來。它提供一個起始值(種子),然後依照運算規則(BinaryOperator),和前面 Stream 的第一個、第二個、第 n 個元素組合。從這個意義上說,字符串拼接、數值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相當於Integer sum = integers.reduce(0, (a, b) -> a+b);也有沒有起始值的情況,這時會把 Stream 的前面兩個元素組合起來,返回的是 Optional。

// 字符串連接,concat = "ABCD"
        String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
        System.out.println(concat);
        // 求最小值,minValue = -3.0
        double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
        System.out.println(minValue);
        // 求和,sumValue = 10, 有起始值
        int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
        System.out.println(sumValue);
        // 求和,sumValue = 10, 無起始值
        sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
        System.out.println(sumValue);
ABCD
-3.0
10
10
ace

上面代碼例如第一個示例的 reduce(),第一個參數(空白字符)即爲起始值,第二個參數(String::concat)爲 BinaryOperator。這類有起始值的 reduce() 都返回具體的對象。而對於第四個示例沒有起始值的 reduce(),由於可能沒有足夠的元素,返回的是 Optional,請留意這個區別。更多內容查看: IBM:Java 8 中的 Streams API 詳解

二、Parallel Streams(並行流)

前面提到過Stream有串行和並行兩種,串行Stream上的操作是在一個線程中依次完成,而並行Stream則是在多個線程上同時執行。

下面的例子展示了是如何通過並行Stream來提升性能:

首先我們創建一個沒有重複元素的大表:

    private int max = 1000000;
    private List<String> values = new ArrayList<>(max);

    public List<String> setValues() {
        for (int i = 0; i < max; i++) {
            UUID uuid = UUID.randomUUID();
            this.values.add(uuid.toString());
        }
        return this.values;
    }

我們分別用串行和並行兩種方式對其進行排序,最後看看所用時間的對比。

1、Sequential Sort(串行排序)

//Sequential Sort(串行排序)
long t0 = System.nanoTime();
long count = streamTest.setValues().stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1-t0);
System.out.println(String.format("串行排序所用的時間--sequential sort took: %d ms",millis));
1000000
串行排序所用的時間--sequential sort took: 2778 ms

2、Parallel Sort(並行排序)

//Parallel Sort(並行排序)
streamTest.values.clear();
long t2 = System.nanoTime();
long count2 = streamTest.setValues().parallelStream().sorted().count();
System.out.println(count2);
long t3 = System.nanoTime();
long millis2 = TimeUnit.NANOSECONDS.toMillis(t3 - t2);
System.out.println(String.format("並行排序所用的時間--parallel sort took: %d ms", millis2));

1000000
並行排序所用的時間--parallel sort took: 1453 ms

上面兩個代碼幾乎是一樣的,但是並行版的快了 50% 左右,唯一需要做的改動就是將 stream() 改爲parallelStream()

應該在什麼時候使用Parallel Streams

  1. 確保要執行的任務對線程環境沒有依賴
  2. 任務消耗時間長/數據量大到不用思考是否要用parallel
  3. 結果沒有順序要求
  4. 只是對數據進行操作不需要考慮死鎖、變量共享等多線程問題

濫用Parallel Streams也可能導致一些問題,可以看一下這篇文章不要濫用parallel stream


完整的測試運行代碼:

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

/**
 * 測試stream
 * @author XieRW
 * @email [email protected]
 */
public class StreamTest {

    private List<String> stringList = new ArrayList<>();

    private int max = 1000000;
    private List<String> values = new ArrayList<>(max);

    public List<String> setValues() {
        for (int i = 0; i < max; i++) {
            UUID uuid = UUID.randomUUID();
            this.values.add(uuid.toString());
        }
        return this.values;
    }
    public List<String> list() {
        stringList.add("ddd2");
        stringList.add("aaa2");
        stringList.add("bbb1");
        stringList.add("aaa1");
        stringList.add("bbb3");
        stringList.add("ccc");
        stringList.add("bbb2");
        stringList.add("ddd1");
        return stringList;
    }

    public static void main(String[] args) {
        //創建streamTest對象
        StreamTest streamTest = new StreamTest();
        //給list賦值
        List<String> list = streamTest.list();
        System.out.println(list.parallelStream());
        //測試filter過濾和foreach
        list
                .stream()
                .filter(s -> s.startsWith("a"))
                .forEach(System.out::println);

        System.out.println("filter==========分界線==========sort");

        //測試sort排序,sort函數不帶參數默認排序也是從小到大
        list.
                stream().
                sorted((s1, s2) -> s1.compareTo(s2)).
                filter(s -> s.startsWith("a")).
                forEach(System.out::println);
        //sort不會改變list原本的順序,filter也是
        System.out.println(list);

        System.out.println("sort==========分界線==========map");

        //測試map,將元素根據指定的Function接口來依次轉換成另外的對象
        list.
                stream().
                map(String::toUpperCase).
                sorted((a, b) -> b.compareTo(a)).
                forEach(System.out::println);

        System.out.println("map==========分界線==========Match");

        //Match匹配
        boolean anyStartWithA = list.stream().anyMatch(s -> s.startsWith("a"));
        System.out.println(anyStartWithA);

        boolean allStartWithA = list.stream().allMatch(s -> s.startsWith("a"));
        System.out.println(allStartWithA);

        boolean noneStartWithZ = list.stream().noneMatch(s -> s.startsWith("z"));
        System.out.println(noneStartWithZ);

        System.out.println("Match==========分界線==========count");

        //count計數
        long startWithB = list.stream().filter(s -> s.startsWith("b")).count();
        System.out.println(startWithB);

        //Reduce規約,允許通過指定的函數來將stream中的多個元素規約爲一個元素,規約後的結果是通過Optional 接口表示的
        Optional<String> reduce = list.stream().sorted().reduce((a, b) -> a + "#" + b);
        System.out.println(reduce);

        System.out.println("count==========分界線==========Stream");

        //Stream接口
        // 字符串連接,concat = "ABCD"
        String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
        System.out.println(concat);
        // 求最小值,minValue = -3.0
        double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
        System.out.println(minValue);
        // 求和,sumValue = 10, 有起始值
        int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
        System.out.println(sumValue);
        // 求和,sumValue = 10, 無起始值
        sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
        System.out.println(sumValue);
        // 過濾,字符串連接,concat = "ace"
        concat = Stream.of("a", "B", "c", "D", "e", "F").
                filter(x -> x.compareTo("Z") > 0).
                reduce("", String::concat);
        System.out.println(concat);

        System.out.println("Stream==========分界線==========Sequential Sort(串行排序)");

        //Sequential Sort(串行排序)
        long t0 = System.nanoTime();
        long count = streamTest.setValues().stream().sorted().count();
        System.out.println(count);
        long t1 = System.nanoTime();
        long millis = TimeUnit.NANOSECONDS.toMillis(t1-t0);
        System.out.println(String.format("串行排序所用的時間--sequential sort took: %d ms",millis));

        //Parallel Sort(並行排序)
        streamTest.values.clear();
        long t2 = System.nanoTime();
        long count2 = streamTest.setValues().parallelStream().sorted().count();
        System.out.println(count2);
        long t3 = System.nanoTime();
        long millis2 = TimeUnit.NANOSECONDS.toMillis(t3 - t2);
        System.out.println(String.format("並行排序所用的時間--parallel sort took: %d ms", millis2));
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章