前言:對大數據量的集合的循環處理,stream擁有極大的優點,徹底能夠用stream去代替for循環。html
先說下Stream的優點:它是java對集合操做的優化,相較於迭代器,使用Stream的速度很是快,而且它支持並行方式處理集合中的數據,默認狀況能充分利用cpu的資源。同時支持函數式編程,代碼很是簡潔。java
Stream是一種用來計算數據的流,它自己並無存儲數據。你能夠認爲它是對數據源的一個映射或者視圖。sql
它的工做流程是:獲取數據源->進行一次或屢次邏輯轉換操做->進行歸約操做造成新的流(最後能夠將流轉換成集合)。編程
Stream的建立須要一個數據源(一般是一個容器或者數組):api
例1:Stream<String> stream = Stream.of("I", "got", "you", "too");數組
例2:String [] strArray = new String[] {"a", "b", "c"};app
stream = Arrays.stream(strArray);ide
例3:List<String> list = Arrays.asList(strArray);函數式編程
stream = list.stream();函數
流的操做類型分2種:中間操做與聚合操做。
中間操做就是對容器的處理過程,包括:排序(sorted...),篩選(filter,limit,distinct...),映射(map,flatMap...)等
2.1.1 排序操做(sorted):(參考:http://www.javashuo.com/article/p-xcyazmue-bg.html)
sorted提供了2個接口:
一、sorted()
默認使用天然序排序, 其中的元素必須實現
Comparable
接口 。
sorted(Comparator<? super T> comparator)
:咱們可使用lambada 來建立一個
Comparator
實例。能夠按照升序或着降序來排序元素。
好比:將一些字符串在地址中按出現的順序排列:
String address = "中山北路南京大學仙林校區";
List<String> aList = new ArrayList<>();
aList.add("南京");
aList.add("大學");
aList.add("仙林校區");
aList.add("仙林大學城");
aList.add("中山北路");
aList.stream().sorted(
Comparator.comparing(a->address.indexOf(a))
).forEach(System.out :: println);
也能夠像下面這樣不使用比較器:
aList.stream().sorted( (a,b)->address.IndexOf(a)-address.IndexOf(b) ).forEach(System.out :: println);//由大到小排序
輸出結果:
注:1.這裏仙林大學城這個字段沒有出現,因此序號是-1,被排在最前面。
2.Comparator.comparing();這個是比較器提供的一個方法,它返回的也是一個比較器,源碼以下:
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
2.1.2 篩選操做(filter):
上一步中,咱們把一些字符串,按照在地址中出現的順序排序。
接下來咱們可能想要進行篩選,把不在地址中,可是indexof爲「-1」,排在最前面的數據篩選掉:
filter能夠對集合進行篩選,它的參數能夠是一個lambda表達式,流中的數據將會經過該lambda表達式返回新的流。
這裏Stream有一個特性很重要,它像一個管道,能夠將多個操做鏈接起來,並只執行一次for循環,這樣大大提升了效率,即便第二次的流操做須要第一次流操做的結果,時間複雜度也只有一個for循環:
因而我能夠在前面加個filter(),這樣把「-1」過濾掉:
String address = "中山北路南京大學仙林校區"; List<String> aList = new ArrayList<>(); aList.add("南京"); aList.add("大學"); aList.add("仙林校區"); aList.add("仙林大學城"); aList.add("中山北路"); aList.stream().filter(a->address.indexOf(a)!=-1)
.sorted(
Comparator.comparing(a->address.indexOf(a))
).forEach(System.out :: println);
輸出結果:
注:foreach是一個終端操做,參數也是一個函數,它會迭代中間操做完成後的每個數據,這裏它將每一個不爲空的元素打印出來。
其它的過濾操做還包括:
limit(long maxSize):得到指定數量的流。
distinct():經過hashCode和equals去除重複元素。
2.1.3 映射操做(map):
映射操做,就像一個管道,能夠將流中的元素經過一個函數進行映射,返回一個新的元素。
這樣遍歷映射,最終返回一個新的容器,注意:這裏返回的新容器數據類型能夠不與原容器類型相同:
舉個例子:咱們將address中每一個元素的位置找出,並返回一個int類型的存儲位置信息的數組:
@Test public void test() { String address = "中山北路南京大學仙林校區"; List<String> aList = new ArrayList<>(); aList.add("南京"); aList.add("大學"); aList.add("仙林校區"); aList.add("仙林大學城"); aList.add("中山北路"); List<Integer> aIntegers =aList.stream() .map(str->mapFunc(address, str)).collect(Collectors.toList()); System.out.println(aIntegers);//.forEach(System.out :: println);
} private int mapFunc(String address,String str) { return address.indexOf(str); }
結果以下:
以前的中間操做只是對流中數據的處理,最終咱們仍是要將它們整合輸出爲一個結果,好比,返回一個最大值,返回一個新的數組,或者將全部元素進行分組等,這就是規約(末端)操做的做用。
咱們經常使用的末端操做函數有Reduce()和collect();
reduce就是減小的意思,它會將集合中的全部值根據規則計算,最後只返回一個結果。
它有三個變種,輸入參數分別是一個參數、二個參數以及三個參數;
1.一個參數的Reduce
它的參數就是一個函數接口:Optional<T> reduce(BinaryOperator<T> accumulator)
好比,咱們找出數組中長度最大的一個數:
public void test() { String address = "中山北路南京大學仙林校區"; List<String> aList = new ArrayList<>(); aList.add("南京"); aList.add("大學"); aList.add("仙林校區"); aList.add("仙林大學城"); aList.add("中山北路"); Optional<String> a =aList.stream() .reduce((s1, s2) -> s1.length()>=s2.length() ? s1 : s2); System.out.println(a.get());//仙林大學城 }
這裏的Optional<T>就是一個容器,它能夠避免空指針,具體能夠百度,這裏也能夠返回一個String的。
2.兩個參數的Reduce
T reduce(T identity, BinaryOperator<T> accumulator)
2個參數其實除了一個函數接口之外,還包括一個固定的初始化的值,它會做爲容器的第一個元素進入計算過程:
例:將每一個字符串拼接,並在以前加上「value:」:
public void test() { String address = "中山北路南京大學仙林校區"; List<String> aList = new ArrayList<>(); aList.add("南京"); aList.add("大學"); aList.add("仙林校區"); aList.add("仙林大學城"); aList.add("中山北路"); String t="value:"; String a =aList.stream() .reduce(t, new BinaryOperator<String>() { @Override public String apply(String s, String s2) { return s.concat(s2); } }); System.out.println(a); }
結果以下:
3.三個參數的狀況主要是在並行(parallelStream)狀況下使用:能夠參考(https://blog.csdn.net/icarusliu/article/details/79504602),有須要能夠了解下。
collect是一個很是經常使用的末端操做,它自己的參數很複雜,有3個:
<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner);
還好,考慮到咱們平常使用,java8提供了一個收集器(Collectors),它是專門爲collect方法量身打造的接口:
咱們經常使用collect將流轉換成List,Map或Set:
1.轉換成list:
Stream<String> stream = Stream.of("I", "love", "you", "too");
List<String> list = stream.collect(Collectors.toList());
2.轉換成Map:
咱們可使用Collector.toMap()接口:Collectors.toMap(keyMapper, valueMapper),這裏就須要咱們指定key和value分別是什麼。
例:咱們將數組中的字符串做爲key,字符串長度做爲value,生成一個map:
String address = "中山北路南京大學仙林校區"; List<String> aList = new ArrayList<>(); aList.add("南京"); aList.add("大學"); aList.add("仙林校區"); aList.add("仙林大學城"); aList.add("中山北路"); String t="value:"; Map<String, Integer> maps = aList.stream().collect(Collectors.toMap(Function.identity(), String::length)); System.out.println(maps);
打印結果:
{中山北路=4, 大學=2, 仙林大學城=5, 仙林校區=4, 南京=2}
一般,咱們在進行分組操做的時候也會將容器轉換爲Map,這裏也說明一下:Collectors.groupingBy(classifier)
groupingBy與sql的group by相似,就是一個分組函數,
例:咱們將數組中的字符串按長度分組:
String address = "中山北路南京大學仙林校區"; List<String> aList = new ArrayList<>(); aList.add("南京"); aList.add("大學"); aList.add("仙林校區"); aList.add("仙林大學城"); aList.add("中山北路"); String t="value:"; Map<Integer, List<String>> maps = aList.stream().collect(Collectors.groupingBy(String::length)); System.out.println(maps);
打印結果:
{2=[南京, 大學], 4=[仙林校區, 中山北路], 5=[仙林大學城]}
findFirst:返回第一個元素,常與orElse一塊兒用: Stream.findFirst().orElse(null):返回第一個,若是沒有則返回null
allMatch:檢查是否匹配全部元素:Stream.allMatch(str->str.equals("a"))
anyMatch:檢查是否至少匹配一個元素.
2.流的末端操做只能有一次: 當這個操做執行後,流就被使用「光」了,沒法再被操做。因此這一定是流的最後一個操做。以後若是想要操做就必須新打開流。
關於流被關閉不能再操做的異常:
這裏曾經遇到過一個錯誤:stream has already been operated upon or closed
意思是流已經被關閉了,這是由於當咱們使用末端操做以後,流就被關閉了,沒法再次被調用,若是咱們想重複調用,只能從新打開一個新的流。