一、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
- 確保要執行的任務對線程環境沒有依賴
- 任務消耗時間長/數據量大到不用思考是否要用parallel
- 結果沒有順序要求
- 只是對數據進行操作不需要考慮死鎖、變量共享等多線程問題
濫用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));
}
}