javaSE8的流库总结
前言
本篇博客對 java 8 的流庫進行一個總結
1. 從迭代到流
在處理集合時,我們通常會迭代遍歷它的元素,并在每個元素上執行某項操作,列如假設我們想統計某本書的所有長單詞數(單詞長度大于10):
package com.dave.steams;import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;/*** @author dave* @date 2021/1/3 22:01* @description 使用流統計單詞數*/
public class CountLongWords {public static void main(String[] args) throws IOException {String contents = new String(Files.readAllBytes(Paths.get("G:/javaFileBooks/The Nightingale and the Rose.txt")),StandardCharsets.UTF_8);List<String> words = Arrays.asList(contents.split("\\PL+"));long count = 0;// 迭代遍歷單詞長度大于10的單詞數for (String word : words) {if (word.length() > 10){count ++;}}System.out.println(count);}
}
如果使用流的話,迭代部分的操作將是這樣的:
count = words.stream().filter(word -> word.length() > 10).count();
- 流的操作比起迭代的操作更易于閱讀,通過方法名就可以知道代碼的操作時干啥的。
- 循環迭代是指定操作的順序的,流可以并行的計算,只要保證結果是正確的即可。
- 流并不存儲元素。這些元素可能存儲在底層的集合中。
- 流的操作不會修改數據源。列如,filter 方法不會從流中將元素移除,而是會重新生成一個新的流,其不包括被過濾了的流。
- 流的操作盡可能使惰性執行的。這意味著直至需要結果時,操作才會執行,并得到想要的結果時就會停止過濾。
使用并行流進行統計長單詞操作:
count = words.parallelStream().filter(word -> word.length() > 10).count();
操作流時的典型流程:
- 創建一個流
- 指定將初始流轉換為其它流的中間操作。
- 應用終止操作,從而產生結果。這個操作會強制執行之前的惰性操作,從此之后,這個流就不能使用了。
2. 流的創建
Collection 接口的 stream 方法,所有的集合使用 stream() 就可以創建一個流,使用 parallelStream() 就可以創建一個并行流。
2.1 Steam.of()
// 產生一個元素為其給定值的流,事實上是調用 Arrays.stream(values) 實現的
public static<T> Stream<T> of(T... values) {return Arrays.stream(values);}
2.2 Arrays.stream()
// 返回以指定數組為源的順序Stream
public static <T> Stream<T> stream(T[] array) {return stream(array, 0, array.length);}
2.3 Array.steam(array, from, to)
// 返回以指定數組的指定范圍為源的順序Stream 。
public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);}
2.4 Stream.empty()
// 返回一個空的順序Stream 。
public static<T> Stream<T> empty() {return StreamSupport.stream(Spliterators.<T>emptySpliterator(), false);}
2.5 Stream.generate()
// 返回無限順序無序流,其中每個元素由提供的Supplier生成。 這適用于生成恒定流,隨機元素流等。
public static<T> Stream<T> generate(Supplier<T> s) {Objects.requireNonNull(s);return StreamSupport.stream(new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);}
// 生成1000個隨機數
Stream.generate(Math::random).limit(1000).collect(Collectors.toList())
3. filter、map和flatMap 方法
3.1 filter
filter 方法會引元是 Predicate, 即從 T 到 Boolean 的函數。對流按照某種條件進行過濾。
count = words.stream().filter(word -> word.length() > 10).count();
3.2 map
map 方法為按某種方式來轉化流中的值。
// 將單詞大寫轉換成小寫
List<String> lowerCaseWords = words.parallelStream().map(String::toLowerCase).collect(Collectors.toList());
3.3 flatMap
flatMap 方法將所有的流合并成一個流
public static void main(String[] args) {/*** 獲取所有單詞的字母集合* 1. map 方法獲取到的是所有單詞流的集合* 2. flatMap 方法獲取到的是所有單詞字母的集合*/List<Stream<String>> result = words.stream().map(CountLongWords::letters).collect(Collectors.toList());System.out.println(result); // 輸出:[java.util.stream.ReferencePipeline$Head@b1bc7ed, java.util.stream.ReferencePipeline$Head@7cd84586, java.util.stream.ReferencePipeline$Head@30dae81,...]List<String> flatMapResult = words.stream().flatMap(CountLongWords::letters).collect(Collectors.toList());System.out.println(flatMapResult);//輸出: [T, h, e, N, i, g, h, t, i, n, g, a, l, e, a, n, d, t, h, e, R, o, ...]}/*** 返回單詞的字母流* @param s 單詞* @return Stream<String>*/private static Stream<String> letters(String s){ArrayList<String> result = new ArrayList<>();for (int i = 0; i < s.length(); i++) {result.add(s.substring(i,i+1));}return result.stream();}
4. 抽取子流和連接流
4.1 stream.limit(n)
調用 stream.limit(n) 會返回一個新的流,它在 n 個元素之后結束(原來的流更短,那么就會在流結束時結束)。這個方法對裁剪無限流尺寸會特別有用,如:
Stream<String> words = Stream.generate(Math::random).limit(1000);
4.2 stream.skip(n)
stream.skip(n) 正好與 stream.limit(n) 方法相反,它是丟棄前 n 個元素。
4.3 stream.concat(a,b)
將兩個流連接起來,第一個流不能是無限流,否則第二個流將永遠不能得到執行。
5. 其它的流轉化
// 產生一個流,包含當前流中所有不同元素
Stream<T> distinct();
// 產生一個流,它的元素是當前流中的所有元素按照順序排序的
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
// 產生一個流,它與當前流中的元素相同,在獲取每個元素時,會將其傳遞給 action
Stream<T> peek(Consumer<? super T> action);
6. 簡單約簡
從流中獲取結果的方法被稱為約簡,約簡是一種終結操作,它們會將流約簡為可以在程序中使用的非流值。前面的count 方法就是一種簡單約簡方法。
常用的簡單約簡方法:
// 使用給定的比較強指定的排序規則返回流的最大元素
Optional<T> max(Comparator<? super T> comparator)
// 使用給定的比較強指定的排序規則返回流的最小元素
Optional<T> min(Comparator<? super T> comparator)
// 返回流的第一個元素
Optional<T> findFrist()
// 返回流的任意一個元素
Optional<T> findAny()// 分別在這個流中的任意元素、所有元素和沒有任何元素匹配給定斷言時返回 true
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
7. 收集結果
使用迭代器將某個函數應用于每個流中
// 輸出流中的每個元素
stream.forEach(System.out::println);
// 將流轉化為數組
String[] result = stream.toArray(String[]::new);
針對將流中的元素收集到另一個目標中,可以使用一個便捷的方法: collect , 它會接受一個 Collector 接口的實例。
Collectors 類提供了大量用于生成公共收集器的工廠方法。列如:
// 將流收集到 list 集合中
List<String> result = stream.collect(Collectors.toList());
// 將流收集到 ser 集合中
Set<String> result = stream.collect(Collectors.toSet());
// 收集到指定集合中
TreeSet<String> result = stream.collect(Collectors.toCollection(TreeSet::new));
// 連接流中的所有元素生成字符串
String result = stream.collect(Collectors.joining());
// 連接流中的所有元素生成字符串,元素間用逗號分開
String result = stream.collect(Collectors.joining(','));
String result = stream.map(Object::toString)collect(Collectors.joining(','));// 求流的總和,平均值,最大值,最小值
// 求所有單詞長度的總和,平均值,最大值,最小值
IntSummaryStatistics summary = words.stream().collect(Collectors.summarizingInt(String::length));
long count = summary.getCount();
double average = summary.getAverage();
int max = summary.getMax();
int min = summary.getMin();
8. 收集到映射表中
假設我們有一個 Stream, 并且想要將其收集到一個映射表中(Map 中),就可以使用 Collectors.toMap 方法,列如:
// 以 id 為 key,name 為 value
Map<Integer, String> collect = people.stream().collect(Collectors.toMap(Person::getId, Person::getName));
// 以 id 為鍵,元素本身為值
Map<Integer, Person> collect = people.stream().collect(Collectors.toMap(Person::getId, Function.identity()));
如果有多個元素有相同的鍵,那么就會存在沖突,拋出 IllegalStateException 對象。可以使用第三個引元函數來覆蓋這種行為:
// 存在相同的鍵時,取舊值,不管新值
Map<Integer, Person> collect2 = people.stream().collect(Collectors.toMap(Person::getId, Function.identity(),(existingValue, newValue) -> existingValue));// 將鍵相同的名字放到一個 set 集合中,下面群組有更簡便的方法
Map<Integer, Set<String>> collect = people.stream().collect(Collectors.toMap(Person::getId,p -> Collections.singleton(p.getName()),(existingValue, newValue) -> {Set<String> union = new HashSet<>(existingValue);union.addAll(newValue);return union;}));
9. 群組和分區
// 將鍵相同的名字放到一個 list 集合中
Map<Integer, List<Person>> collect1 = people.stream().collect(Collectors.groupingBy(Person::getId));
當分類函數是斷言函數時(即返回 boolean 值的函數)時,流的元素可以分為兩個列表:該函數返回 true 的元素和其它元素。在這種情況下使用 partitioningBy 比使用 groupingBy 更要高效,如下面代碼將年齡大于20的分為一組,小于20的分為另一組:
Map<Boolean, List<Person>> collect3 = people.stream().collect(Collectors.partitioningBy(p -> p.getAge() > 20));List<Person> greaterThanTwenty= collect3.get(true);
10.下游收集器
groupingBy 方法會產生一個映射表,它的每一個值都是一個列表。如果想要以某種方式來處理這些列表,就需要提供一個下游收集器。列如,如果想要獲得集而不是列表,那么就可以使用 Collectors.toSet 收集器:
Map<Integer, Set<Person>> collect4 = people.stream().collect(Collectors.groupingBy(Person::getId, Collectors.toSet()));
常用的下游收集器:
// 1. counting 會產生收集到的元素的個數,如:
Map<Integer, Long> idCount = people.stream().collect(Collectors.groupingBy(Person::getId, Collectors.counting()));// 2. summing(Int|Long|Double) 會接受一個函數引元,將該函數應用到下游元素中,并計算它們的和。如:
Map<Integer, Integer> ageSum = people.stream().collect(Collectors.groupingBy(Person::getId, Collectors.summingInt(Person::getAge)));// 3. maxBy 和 minBy 會接受一個比較器,并產生下游元素中最大值和最小值。如:
Map<Integer, Optional<Person>> maxAge = people.stream().collect(Collectors.groupingBy(Person::getId, Collectors.maxBy(Comparator.comparing(Person::getAge))));// 4. mapping 方法會產生將函數應用到下游結果的收集器,并將函數值傳遞給另一個收集器,列如:按照州將城市群組在一起。每個州的內部,我們生成各個城市的名字,并按照最大長度簡約。
Map<Integer, Optional<Person>> stateToLongestCityName = cities.stream().collect(Collectors.groupingBy(City::getState, Collectors.mapping(City::getName, Collectors.maxBy(Comparator.comparing(Person::getAge))));
11. 并行流
使用 Collection.parallelStream() 方法可以從任何集合中獲取并行流
流使得并行處理快操作變得很容易,這個過程幾乎是自動的,但需要遵守一些規則:
- 只要在終結方法執行時,流出于并行模式,那么所有的中間流操作都將被并行化。
- 當流并行運行時,目標的返回結果與順序執行時返回的結果相同。
- 流應該可以被高效的分成若干個子部分。
- 不要將所有流都轉化為并行流。只有在對已經位于內存中的數據執行大量計算操作時,才應該使用并行流。
- 傳遞給并行流的操作的任何函數都是可以安全地并行執行,遠離易變狀態
一段糟糕的無法完成的任務:對字符串流中的所有短單詞計數:
int[] shortWords = new int[12];words.parallelStream().forEach(s->{if (s.length() < 12) {shortWords[s.length()] ++;}});
這是一段很糟糕的代碼,傳遞給 forEach 的函數會在多個并發線程中運行,每個都會更新共享的數組。如果多次運行這個程序,很可能每次的計數結果都不一樣。
改進:
Map<Integer, Long> shortWordCounts = words.parallelStream().filter(s -> s.length() < 12).collect(Collectors.groupingBy((String::length), Collectors.counting()));
總結
以上是生活随笔為你收集整理的javaSE8的流库总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: idea中项目失去svn控制
- 下一篇: 从底层吃透java内存模型(JMM)、v