Java8旨在幫助程序員寫出更好的代碼,
其對核心類庫的改進也是關鍵的一部分,Stream是Java8種處理集合的抽象概念,
它能夠指定你但願對集合的操做,可是執行操做的時間交給具體實現來決定。java
Java語言中集合是使用最多的API,幾乎每一個Java程序都會用到集合操做,
這裏的Stream和IO中的Stream不一樣,它提供了對集合操做的加強,極大的提升了操做集合對象的便利性。程序員
集合對於大多數編程任務而言都是基本的,爲了解釋集合是怎麼工做,咱們想象一下當下最火的外賣APP,
當咱們點菜的時候須要按照距離、價格、銷量等進行排序後篩選出本身滿意的菜品。
你可能想選擇距離本身最近的一家店鋪點菜,儘管用集合能夠完成這件事,但集合的操做遠遠算不上完美。編程
假如讓你編寫上面示例中的代碼,你可能會寫出以下:c#
// 店鋪屬性
public class Property {
String name;
// 距離,單位:米
Integer distance;
// 銷量,月售
Integer sales;
// 價格,這裏簡單起見就寫一個級別表明價格段
Integer priceLevel;
public Property(String name, int distance, int sales, int priceLevel) {
this.name = name;
this.distance = distance;
this.sales = sales;
this.priceLevel = priceLevel;
}
// getter setter 省略
}
複製代碼
我想要篩選距離我最近的店鋪,你可能會寫下這樣的代碼:數組
public static void main(String[] args) {
Property p1 = new Property("叫了個雞", 1000, 500, 2);
Property p2 = new Property("張三丰餃子館", 2300, 1500, 3);
Property p3 = new Property("永和大王", 580, 3000, 1);
Property p4 = new Property("肯德基", 6000, 200, 4);
List<Property> properties = Arrays.asList(p1, p2, p3, p4);
Collections.sort(properties, (x, y) -> x.distance.compareTo(y.distance));
String name = properties.get(0).name;
System.out.println("距離我最近的店鋪是:" + name);
}
複製代碼
這裏也使用了部分lambda表達式,在Java8以前你可能寫的更痛苦一些。
要是要處理大量元素又該怎麼辦呢?爲了提升性能,你須要並行處理,並利用多核架構。
但寫並行代碼比用迭代器還要複雜,並且調試起來也夠受的!bash
但Stream中操做這些東西固然是很是簡單的,小試牛刀:架構
// Stream操做
String name2 = properties.stream()
.sorted(Comparator.comparingInt(x -> x.distance))
.findFirst()
.get().name;
System.out.println("距離我最近的店鋪是:" + name);
複製代碼
新的API對全部的集合操做都提供了生成流操做的方法,寫的代碼也行雲流水,咱們很是簡單的就篩選了離我最近的店鋪。
在後面咱們繼續講解Stream更多的特性和玩法。併發
當你處理集合時,一般會對它進行迭代,而後處理返回的每一個元素。好比我想看看月銷量大於1000的店鋪個數。app
int count = 0;
for (Property property : properties) {
if(property.sales > 1000){
count++;
}
}
複製代碼
上面的操做是可行的,可是當每次迭代的時候你須要些不少重複的代碼。將for循環修改成並行執行也很是困難,
須要修改每一個for的實現。函數
從集合背後的原理來看,for循環封裝了迭代的語法糖,首先調用iterator方法,產生一個Iterator對象,
而後控制整個迭代,這就是外部迭代。迭代的過程經過調用Iterator對象的hasNext和next方法完成。
int count = 0;
Iterator<Property> iterator = properties.iterator();
while(iterator.hasNext()){
Property property = iterator.next();
if(property.sales > 1000){
count++;
}
}
複製代碼
而迭代器也是有問題的。它很難抽象出未知的不能操做;此外它本質上仍是串行化的操做,整體來看使用
for循環會將行爲和方法混爲一談。
另外一種辦法是使用內部迭代完成,properties.stream()該方法返回一個Stream而不是迭代器。
long count = properties.stream()
.filter(p -> p.sales > 1000)
.count();
複製代碼
上述代碼是經過Stream API完成的,咱們能夠把它理解爲2個步驟:
爲了找出銷量大於1000的店鋪,須要先作一次過濾:filter,你能夠看看這個方法的入參就是前面講到的Predicate斷言型函數式接口,
測試一個函數完成後,返回值爲boolean。
因爲Stream API的風格,咱們沒有改變集合的內容,而是描述了Stream的內容,最終調用count()方法計算出Stream
裏包含了多少個過濾以後的對象,返回值爲long。
你已經知道Java8種在Collection接口添加了Stream方法,能夠將任何集合轉換成一個Stream。
若是你操做的是一個數組可使用Stream.of(1, 2, 3)方法將它轉換爲一個流。
也許有人知道JDK7中添加了一些類庫如Files.readAllLines(Paths.get("/home/biezhi/a.txt"))這樣的讀取文件行方法。
List做爲Collection的子類擁有轉換流的方法,那麼咱們讀取這個文本文件到一個字符串變量中將變得更簡潔:
String content = Files.readAllLines(Paths.get("/home/biezhi/a.txt")).stream()
.collect(Collectors.joining("\n"));
複製代碼
這裏的collect是後面要講解的收集器,對Stream進行了處理後獲得一個文本文件的內容。
JDK8也爲咱們提供了一些便捷的Stream相關類庫:
建立一個流是很簡單的,下面咱們試試用建立好的Stream作一些操做吧。
java.util.stream.Stream中定義了許多流操做的方法,爲了更好的理解Stream API掌握它經常使用的操做很是重要。
流的操做其實能夠分爲兩類:處理操做、聚合操做。
filter看名字也知道是過濾的意思,咱們一般在篩選數據的時候用到,頻率很是高。
filter方法的參數是Predicate<T> predicate即一個從T到boolean的函數。
篩選出距離我在1000米內的店鋪
properties.stream()
.filter(p -> p.distance < 1000)
複製代碼
篩選出名稱大於5個字的店鋪
properties.stream()
.filter(p -> p.name.length() > 5);
複製代碼
有時候咱們須要將流中處理的數據類型進行轉換,這時候就可使用map方法來完成,將流中的值轉換爲一個新的流。
列出全部店鋪的名稱
properties.stream()
.map(p -> p.name);
複製代碼
傳給map的lambda表達式接收一個Property類型的參數,返回一個String。
參數和返回值沒必要屬於同一種類型,可是lambda表達式必須是Function接口的一個實例。
有時候咱們會遇到提取子流的操做,這種狀況用的很少可是遇到flatMap將變得更容易處理。
例如咱們有一個List<List<String>>結構的數據:
List<List<String>> lists = new ArrayList<>();
lists.add(Arrays.asList("apple", "click"));
lists.add(Arrays.asList("boss", "dig", "qq", "vivo"));
lists.add(Arrays.asList("c#", "biezhi"));
複製代碼
要作的操做是獲取這些數據中長度大於2的單詞個數
lists.stream()
.flatMap(Collection::stream)
.filter(str -> str.length() > 2)
.count();
複製代碼
在不使用flatMap前你可能須要作2次for循環。這裏調用了List的stream方法將每一個列表轉換成Stream對象,
其餘的就和以前的操做同樣。
Stream中經常使用的操做之一是求最大值和最小值,Stream API 中的max和min操做足以解決這一問題。
咱們須要篩選出價格最低的店鋪:
Property property = properties.stream()
.max(Comparator.comparingInt(p -> p.priceLevel))
.get();
複製代碼
查找Stream中的最大或最小元素,首先要考慮的是用什麼做爲排序的指標。
以查找價格最低的店鋪爲例,排序的指標就是店鋪的價格等級。
爲了讓Stream對象按照價格等級進行排序,須要傳給它一個Comparator對象。
Java8提供了一個新的靜態方法comparingInt,使用它能夠方便地實現一個比較器。
放在之前,咱們須要比較兩個對象的某項屬性的值,如今只須要提供一個存取方法就夠了。
一般咱們處理完流以後想查看一下結果,好比獲取總數,轉換結果,在前面的示例中你發現調用了
filter、map以後沒有下文了,後續的操做應該調用Stream中的collect方法完成。
獲取距離我最近的2個店鋪
List<Property> properties = properties.stream()
.sorted(Comparator.comparingInt(x -> x.distance))
.limit(2)
.collect(Collectors.toList());
複製代碼
獲取全部店鋪的名稱
List<String> names = properties.stream()
.map(p -> p.name)
.collect(Collectors.toList());
複製代碼
獲取每一個店鋪的價格等級
Map<String, Integer> map = properties.stream()
.collect(Collectors.toMap(Property::getName, Property::getPriceLevel));
複製代碼
全部價格等級的店鋪列表
Map<Integer, List<Property>> priceMap = properties.stream()
.collect(Collectors.groupingBy(Property::getPriceLevel));
複製代碼
併發是兩個任務共享時間段,並行則是兩個任務在同一時間發生,好比運行在多核CPU上。
若是一個程序要運行兩個任務,而且只有一個CPU給它們分配了不一樣的時間片,那麼這就是併發,而不是並行。
並行化是指爲縮短任務執行時間,將一個任務分解成幾部分,而後並行執行。
這和順序執行的任務量是同樣的,區別就像用更多的馬來拉車,花費的時間天然減小了。
實際上,和順序執行相比,並行化執行任務時,CPU承載的工做量更大。
數據並行化是指將數據分紅塊,爲每塊數據分配單獨的處理單元。
仍是拿馬拉車那個例子打比方,就像從車裏取出一些貨物,放到另外一輛車上,兩輛馬車都沿着一樣的路徑到達目的地。
當須要在大量數據上執行一樣的操做時,數據並行化很管用。
它將問題分解爲可在多塊數據上求解的形式,而後對每塊數據執行運算,最後將各數據塊上獲得的結果彙總,從而得到最終答案。
人們常常拿任務並行化和數據並行化作比較,在任務並行化中,線程不一樣,工做各異。
咱們最常遇到的JavaEE應用容器即是任務並行化的例子之一,每一個線程不光能夠爲不一樣用戶服務,
還能夠爲同一個用戶執行不一樣的任務,好比登陸或往購物車添加商品。
流使得計算變得容易,它的操做也很是簡單,但你須要遵照一些約定。默認狀況下咱們使用集合的stream方法
建立的是一個串行流,你有兩種辦法讓他變成並行流。
咱們來用具體的例子來解釋串行和並行流
串行化計算
篩選出價格等級小於4,按照距離排序的2個店鋪名
properties.stream()
.filter(p -> p.priceLevel < 4)
.sorted(Comparator.comparingInt(Property::getDistance))
.map(Property::getName)
.limit(2)
.collect(Collectors.toList());
複製代碼
調用 parallelStream 方法即能並行處理
properties.parallelStream()
.filter(p -> p.priceLevel < 4)
.sorted(Comparator.comparingInt(Property::getDistance))
.map(Property::getName)
.limit(2)
.collect(Collectors.toList());
複製代碼
讀到這裏,你們的第一反應多是當即將手頭代碼中的stream方法替換爲parallelStream方法,
由於這樣作簡直太簡單了!先別忙,爲了將硬件物盡其用,利用好並行化很是重要,但流類庫提供的數據並行化只是其中的一種形式。
咱們先要問本身一個問題:並行化運行基於流的代碼是否比串行化運行更快?這不是一個簡單的問題。
回到前面的例子,哪一種方式花的時間更多取決於串行或並行化運行時的環境。