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


前言

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

流和集合的前世今生

概念的差異

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

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

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

數據修改

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

內部迭代和外部迭代

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

循環遍歷

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

惰性求值(懶漢or餓漢)

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

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

file

特性解讀

兩種迭代方式

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

file

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

中間操作和終端操作

在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的常用操作有哪些?

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM