天天看點

java8 list 行轉列_俠說java8--Stream流操作學習筆記,都在這裡了

前言

  • 首次接觸到Stream的時候以為它是和InputStream、OutputStream這樣的輸入輸出流的統稱。

流和集合的前世今生

概念的差異

在開發中,我們使用最多的類庫之一就是集合。集合是一種記憶體中的資料結構,用來儲存對象資料,集合中的每個元素都得先算出來才能添加到集合中,相比之下:

集合用特定資料結構(如List,Set或Map)存儲和分組資料。但是,流用于對存儲的資料(例如數組,集合或I / O資源)執行複雜的資料處理操作,例如過濾,比對,映射等。由我們可以知道:

集合主要是存儲資料,而流主要關注對資料的操作。

資料修改

我們可以添加或删除集合中的元素。但是,不能添加或删除流中的元素。流是通過消費資料源的方式,對其執行操作并傳回結果。它不會修改資料源頭。

内部疊代和外部疊代

Java8提供的 Streams的主要特點是我們不必擔心使用流時的疊代。流在背景為我們内部執行疊代。我們隻需關注在資料源上需要執行哪些操作即可。

循環周遊

流隻能周遊一次。如果你周遊該流一次,則将其消耗掉。要再次周遊它,必須再次從資料源中擷取新的流。但是,集合可以周遊多次。

惰性求值(懶漢or餓漢)

相信大家都知道單例模式中的兩種模式,懶漢式和餓漢式,在這裡也可以相似的了解。

集合以餓漢式迅速的建構,即是所有元素都在開始時就進行了計算。但是,流是延遲構造的,即在調用終端操作之前不會去計算中間操作,也就是惰性求值(懶漢式)。

java8 list 行轉列_俠說java8--Stream流操作學習筆記,都在這裡了

特性解讀

兩種疊代方式

上面我們提到了兩種疊代的方式,内部疊代和外部疊代,怎麼來了解呢?在java8之前,我們用的for循序,其實就是外部疊代,顯示的去循環集合中的每一個元素。

java8 list 行轉列_俠說java8--Stream流操作學習筆記,都在這裡了

而内部疊代則是,Stream内部幫我們做了這個操作,并且它還把流的值放到了某個地方,我們隻需要給出相應的指令(map/flatmap/filter),指揮它就行。

java8 list 行轉列_俠說java8--Stream流操作學習筆記,都在這裡了
中間操作和終端操作

在java8中,我們可以把中間操作認為是工廠流水線上的一個勞工,它将産品加工過後,傳回一個新的東西(流),一個新的流。它會讓多個操作可以連接配接起來,一旦流水線上觸發一個終端操作就會執行處理。

惰性 求值的了解

上面提到的兩種操作其實就是惰性求值的解讀。中間操作一般都可以合并起來,在終端操作時一次性全部處理求值。在處理更大的資料或流操作很多時,惰性求值是真正的福音。

因為處理資料時,我們不确定如何使用處理後的資料。直接循環一個很大的集合将始終以性能為代價而告終,其實用戶端可能隻是最終會利用其中的一小部分。或者,根據某些條件過濾一下,它可能甚至不需要利用該資料。惰性求值處理基于按需 政策來幫助我們實作業務功能。

Stream操作案例

String類上提供了有兩個新方法:join和chars,使用join拼接字元串非常友善。

String.join(":", "foobar", "foo", "bar");// => foobar:foo:bar
           

第二種方法chars為字元串的所有字元建立流,可以對這些字元使用流操作:

"foobar:foo:bar"    .chars()    .distinct()    .mapToObj(c -> String.valueOf((char)c))    .sorted()    .collect(Collectors.joining());// => :abfor
           

處理檔案Files最初是在Java 7中作為Java NIO的一部分引入的。JDK 8 API添加了一些其他方法,使我們能夠對檔案使用功能流。

try (Stream stream = Files.list(Paths.get(""))) {    String joined = stream        .map(String::valueOf)        .filter(path -> !path.startsWith("."))        .sorted()        .collect(Collectors.joining("; "));    System.out.println("List: " + joined);}
           

上面的示例列出了目前工作目錄的所有檔案,然後将每個路徑映射到其字元串表示形式。然後将結果過濾,排序并最終加入一個字元串中。

細心的你您可能已經注意到,流的建立被包裝在try / with語句中。流實作了AutoCloseable,在這種情況下,由于有IO操作支援,是以我們确實必須顯式關閉流。

查找檔案

Path start = Paths.get("");int maxDepth = 5;try (Stream stream = Files.find(start, maxDepth, (path, attr) ->        String.valueOf(path).endsWith(".js"))) {    String joined = stream        .sorted()        .map(String::valueOf)        .collect(Collectors.joining("; "));    System.out.println("Found: " + joined);}
           

該方法find接受三個參數:目錄路徑start是初始起點,并maxDepth定義了要搜尋的最大檔案夾深度。第三個參數是比對謂詞,它定義搜尋邏輯。在上面的示例中,我們搜尋所有JavaScript檔案(檔案名以.js結尾)。

讀寫檔案

List lines = Files.readAllLines(Paths.get("res/nashorn1.js"));lines.add("print('foobar');");Files.write(Paths.get("res/nashorn1-modified.js"), lines);
           

用Java 8将文本檔案讀入記憶體并将字元串寫入文本檔案。這些方法的記憶體效率不是很高,因為整個檔案都将被讀取到記憶體中。檔案越大,将使用越多的堆大小。

使用流的注意事項

注意,流隻能使用一次。

public static void main(String[] args) {        String[] array = {"a", "b", "c", "d", "e"};        Stream stream = Arrays.stream(array);        // 消費流        stream.forEach(x -> System.out.println(x));        // 重用流! throws IllegalStateException        long count = stream.filter(x -> "b".equals(x)).count();        System.out.println(count);    }
           

正确的使用方式

public static void main(String[] args) {        String[] array = {"a", "b", "c", "d", "e"};        Supplier> streamSupplier = () -> Stream.of(array);        //擷取新的流        streamSupplier.get().forEach(x -> System.out.println(x));        //擷取另一個流        long count = streamSupplier.get().filter(x -> "b".equals(x)).count();        System.out.println(count);    }
           

過濾空值

Stream language = Stream.of("java", "python", "node", null, "ruby", null, "php");        //List result = language.collect(Collectors.toList());      //使用filter過濾空值        List result = language.filter(x -> x!=null).collect(Collectors.toList());        result.forEach(System.out::println);
           

map映射操作

List alpha = Arrays.asList("a", "b", "c", "d");        //Java8前        List alphaUpper = new ArrayList<>();        for (String s : alpha) {            alphaUpper.add(s.toUpperCase());        }        System.out.println(alpha); //[a, b, c, d]        System.out.println(alphaUpper); //[A, B, C, D]        // Java 8        List collect = alpha.stream().map(String::toUpperCase).collect(Collectors.toList());        System.out.println(collect); //[A, B, C, D]        // map映射操作        List num = Arrays.asList(1,2,3,4,5);        List collect1 = num.stream().map(n -> n * 2).collect(Collectors.toList());        System.out.println(collect1); //[2, 4, 6, 8, 10]
           

分組,計數、排序

//3 apple, 2 banana, others 1        List items =                Arrays.asList("apple", "apple", "banana",                        "apple", "orange", "banana", "papaya");        Map result =                items.stream().collect(                        Collectors.groupingBy(                                Function.identity(), Collectors.counting()                        )                );        Map finalMap = new LinkedHashMap<>();        //map排序        result.entrySet().stream()                .sorted(Map.Entry.comparingByValue()                        .reversed()).forEachOrdered(e -> finalMap.put(e.getKey(), e.getValue()));        System.out.println(finalMap);
           

總結

本篇文章記錄了Stream流操作的一些知識點。來檢測一下,以下問題你是不是都會了呢?

  • Stream流和集合的差別?
  • 解釋内部循環和外部循環?
  • 解釋一下惰性求值?
  • Stream的常用操作有哪些?
歡迎來公衆号【俠夢的開發筆記】 一起交流進步

繼續閱讀