1. 程式人生 > >俠說java8--Stream流操作學習筆記,都在這裡了

俠說java8--Stream流操作學習筆記,都在這裡了

前言

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

流和集合的前世今生

概念的差異

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

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

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

資料修改

我們可以新增或刪除集合中的元素。但是,不能新增或刪除流中的元素。流是通過消費資料來源的方式,對其執行操作並返回結果。它不會修改資料來源頭。

內部迭代和外部迭代

Java8提供的 Streams的主要特點是我們不必擔心使用流時的迭代。流在後臺為我們內部執行迭代。我們只需關注在資料來源上需要執行哪些操作即可。

迴圈遍歷

流只能遍歷一次。如果你遍歷該流一次,則將其消耗掉。要再次遍歷它,必須再次從資料來源中獲取新的流。但是,集合可以遍歷多次。

惰性求值(懶漢or餓漢)

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

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

特性解讀

兩種迭代方式

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

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

中間操作和終端操作

在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<Path> 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<Path> 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<String> 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<String> 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<Stream<String>> 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<String> language = Stream.of("java", "python", "node", null, "ruby", null, "php");

        //List<String> result = language.collect(Collectors.toList());

    //使用filter過濾空值
        List<String> result = language.filter(x -> x!=null).collect(Collectors.toList());

        result.forEach(System.out::println);

map對映操作

 List<String> alpha = Arrays.asList("a", "b", "c", "d");

        //Java8前
        List<String> 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<String> collect = alpha.stream().map(String::toUpperCase).collect(Collectors.toList());
        System.out.println(collect); //[A, B, C, D]

        // map對映操作
        List<Integer> num = Arrays.asList(1,2,3,4,5);
        List<Integer> 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<String> items =
                Arrays.asList("apple", "apple", "banana",
                        "apple", "orange", "banana", "papaya");

        Map<String, Long> result =
                items.stream().collect(
                        Collectors.groupingBy(
                                Function.identity(), Collectors.counting()
                        )
                );

        Map<String, Long> finalMap = new LinkedHashMap<>();

        //map排序
        result.entrySet().stream()
                .sorted(Map.Entry.<String, Long>comparingByValue()
                        .reversed()).forEachOrdered(e -> finalMap.put(e.getKey(), e.getValue()));

        System.out.println(finalMap);

總結

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

  • Stream流和集合的區別?
  • 解釋內部迴圈和外部迴圈?
  • 解釋一下惰性求值?
  • Stream的常用操作有哪些?

歡迎來公眾號【俠夢的開發筆記】 一起交流進步