JDK8特性深刻學習筆記-Stream(4)

Stream

Stream自己是一個接口,裏面的方法大部分都是高階函數。Stream是一個元素序列,支持一些串行和並行的操做。java

  • Stream由3部分構成數組

    • 源:這個Stream是來自哪裏
    • 零個或多箇中間操做:對源的操做,會將當前的流轉化爲另一個流
    • 終止操做
int sum = widgets.stream()
                 .filter(w -> w.getColor() == RED)
                 .mapToInt(w -> w.getWeight())
                 .sum();
  • 流操做的分類數據結構

    • 惰性求值:只有在終止操做被執行時,中間操做纔會被執行,例如只有當sum()執行時,中間的filter和mapToInt纔會被執行。
    • 及早求值:終止操做就是及早求值。

Stream的建立方式

// 第一種
Stream stream1 = Stream.of("a", "b", "c");
// 第二種
String[] myArray = new String[]{"a", "b", "c"};
Stream stream2 = Stream.of(myArray);
Stream stream3 = Arrays.stream(myArray);
// 第三種,經過集合建立Stream對象
List<String> list = Arrays.asList(myArray);
Stream stream4 = list.stream();

Stream帶來的簡化

Stream.of(new int[]{1, 2, 3}).forEach(System.out::println);

IntStream.range(3, 8).forEach(System.out::println);

for循環與Stream的對比

題設條件:有一個List<Integer> list,須要對裏面的元素進行 * 2,並將全部的元素結果相加返回併發

for循環

int sum = 0;
for(Integer i : list){
    sum+= i * 2;
}
System.out.println(sum);

Stream

System.out.println(list.stream().mapToInt(i -> i * 2).sum());
System.out.println(list.stream().map(i -> i * 2).reduce(0, Integer::sum));

Stream源碼

  • 流自己並不存儲值,而是經過管道來獲取值。
  • 流本質是函數式的,對流的操做會生成一個結果,不過並不會修改底層的數據源。
  • 流的每次操做都會生成一個新的流

經過流來構建數組和List

Stream<String> stream = Stream.of("a", "b", "c");
// lambda表達式方式
String[] stringArray = stream.toArray(i -> new String[i]);
List<String> stringList = stream.collect(Collectors.toList());
// 方法引用方式(構造方法引用)
 String[] stringArray2 = stream.toArray(String[]::new);

collect方法

<R> R collect(Supplier<R> supplier,
                BiConsumer<R, ? super T> accumulator,
                BiConsumer<R, R> combiner);
  • collect方法是一個終止操做(只要返回的不是Stream,就是終止操做)
  • collect方法是一個泛形的,接受3個參數
  • collect方法是一個可變的匯聚操做,是經過更新結果的狀態進行合併的,而不是經過替換結果來進行合併
    collect等價於:
R result = supplier.get();
    for (T element : this stream)
        accumulator.accept(result, element);
    return result;

不使用Collectors.toList,而使用collect方法來組裝:dom

// lambda寫法
List<String> stringList2 = stream.collect(() -> new ArrayList<>(),(biList, item) -> biList.add(item),(list1, list2) -> list1.addAll(list2));

// 方法引用寫法
List<String> stringList3 = stream.collect(ArrayList::new, ArrayList::add,ArrayList::addAll);
  • 第一個Supplier構造了ArrayList集合容器
  • 第二個BiConsumer做爲累加器,每次執行的時候都將建立一個List,將stream中的元素添加到這個集合當中
  • 第三個BiConsumer做爲合併器,語義實際上是將第二步得到的每一個list,添加到最終返回的List當中

這裏談談我我的的理解:函數

  1. 第一個構造容器,沒什麼好說的。
  2. 第二個BiConsumer做爲累加器,理論上實現了遍歷stream內元素的操做。
  3. 那麼爲何須要第三步來合併呢,應該在第二步add時就能夠所有完成了纔對。
  4. 因此我猜想第三個BiConsumer做爲合併器,使用場景應該是提供是否支持並行操做使用,若是能支持並行操做,將stream內的元素按照某種規則分配,並行添加完成後,在合併在一塊兒,能提高遍歷的效率。
以上的內容爲我的的猜測。

Stream實例剖析

Collectors.toCollection

public static <T, C extends Collection<T>>
    Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
        return new CollectorImpl<>(collectionFactory, Collection<T>::add,
                                   (r1, r2) -> { r1.addAll(r2); return r1; },
                                   CH_ID);
    }

toCollection接收一個Supplier函數方法,來構造容器性能

實際使用場景:ui

Stream<String> stream = Stream.of("a", "b", "c","c");
List<String> stringList = stream.collect(Collectors.toList());
LinkedList<String> linkedList = stringList.stream()
                                  .collect(Collectors.toCollection(LinkedList::new));
Set<String> set = stringList.stream().collect(Collectors.toCollection(HashSet::new));

FlapMap

與Map差異:this

  • map : 集合中的元素是什麼類型,就返回什麼類型的流
  • Flapmap:集合流中若是包含了子集合,那麼就返回子集合中元素類型的流
Stream<List<String>> listStream = Stream.of(Arrays.asList("hello1", "world1", "test1")
                , Arrays.asList("hello2", "world2", "test3")
                , Arrays.asList("hello3", "world3", "test3"));
        Stream<String> stringStream = listStream.flatMap(i -> i.stream());
        List<String> list = stringStream.collect(Collectors.toList());
        list.stream().forEach(System.out::println);
List<String> list = Arrays.asList("hello world","welcome world","hello welcome");
list.stream().map(item -> item.split(" ")).flatMap(i -> Arrays.stream(i)).distinct().collect(Collectors.toList()).forEach(System.out::println);

flapmap將Stream<String[]> 打平爲 Stream<String>指針

findFirst

findFirst是一箇中斷式的方法,返回Stream的第一個元素,以Optional<T>的形式返回。

Stream<String> stringStream = Stream.gennerate("abc"::new);
Optional<String> optionalS = stringStream.findFirst();
optionalS.ifPresent(System.out::println);

至於爲何findFirst要返回Optional,應該是爲了防止空指針異常,有可能Stream內沒有元素。

Iterate 與 limt

Stream.iterate(2,i -> i * 2).limit(10).forEach(System.out::println);
  • Iterate是一個迭代器,入參第一個爲起始值,第二個是一個Function,會生成一個無限迭代的無限流。
  • limit是一箇中間操做,限制流內元素的個數。

Stream陷阱避免

Stream<Integer> stream = Stream.iterate(1, i -> i + 2)
  .limit(6)
  .filter(i -> i > 2);
System.out.println(stream.skip(3));
System.out.println(stream.count());

這段代碼將會拋出異常:java.lang.IllegalStateException: stream has already been operated upon or closed

緣由是stream每次中間操做,都會返回一個新的stream,而且一個stream只容許被操做一次。

而在執行System.out.println(stream.count());時,stream這個流已經被執行過stream.skip(3)了。

Stream.iterate(1, i -> (i + 1) % 2)
  .distinct()
  .limit(6)
  .forEach(System.out::println);

Stream.iterate(1, i -> (i + 1) % 2)
  .limit(6)
  .distinct()
  .forEach(System.out::println);

第一段段代碼將會無限執行下去,而第二段則不會。

緣由是limit(6)和 distinct()的順序不一樣,distinct後,代碼只會返回2個元素,而limit在等待6個元素,因此iterate將無限執行下去,直到能返回6個元素。

流的短路和併發

並行流能提高多少的性能

List<String> list = new ArrayList<>(5000000);
for (int i = 0; i < 5000000; i++) {
  list.add(UUID.randomUUID().toString());
}
System.out.println("開始排序");
long start = System.nanoTime();
list.stream().sorted().count();
long end = System.nanoTime();
long use = TimeUnit.NANOSECONDS.toMillis(end - start);
System.out.println("使用時間:" + use);

這裏是經過串行流完成500w個uuid排序,在個人電腦上,串行流使用了4秒,而並行流只須要1秒,提高了將近4倍。

Stream是可短路的,是將全部的配置項,例如distinct,sort,map等中間操做,彙總在一塊兒後才執行,而且只要完成要求,後續的元素遍歷將再也不執行,例如findFirst,得到到符合條件的一個元素後,後續元素將再也不獲得遍歷。

Stream 的分組和分區

分組

Student student1 = new Student("A",100,15);
Student student2 = new Student("B",70,20);
Student student3 = new Student("C",80,25);
Student student4 = new Student("A",59,30);

List<Student> list = Arrays.asList(student1,student2,student3,student4);

Map<String, List<Student>> map = list.stream()
.collect(Collectors.groupingBy(Student::getName));

經過Collectors.groupingBy()方法,將Stream<T>的內容經過分組依據 T.xx,返回爲 Map<T.xxx, List<T>>

若是隻須要統計分組後各個依據T.xx的個數,只須要將Collectors.groupingBy(T::xx) 替換爲Collectors.groupingBy(T::xx, Collectors.counting())便可返回Map<T.xxx, Long>

分區

  • 分組:group by:分組將會依照分組依據來分紅多種
  • 分區:partition by:而分區則只會分爲兩種,一種是符合條件,一種是不符合條件(即true和false)。
Map<Boolean, List<Student>> map = list.stream()
                .collect(Collectors.partitioningBy(i -> i.getScore() >= 60));

小結

  • Stream沒有內部存儲,只是經過操做管道從source(數據結構,數組,IO等)抓取數據。
  • Stream毫不修改本身所封裝的底層數據結構的數據,每次操做都返回一個新的Stream
  • 若是遇到返回的類型的容器或數組,則能夠經過flapmap方法來將其扁平化。
  • Stream是支持並行化
  • Stream是能夠無限的,經過iterate方法能夠無限生成。
  • Stream是可短路的。
相關文章
相關標籤/搜索