Java8-流

目錄

  • 簡介
  • 用法
  • 例子
  • 注意點

一. 簡介

流是Java8引入的一個新特性,提供了對集合元素一系列便捷的操做,能夠用不多的代碼實現複雜的功能。
流有兩大類,分別是對象流(Stream),基本數據流(IntStream、LongStream、DoubleStream)。java

二.用法

流的使用一般爲三個步驟:①建立流 ②描述流 ③求值數組

建立流

經過Stream靜態工廠方法來建立,Stream類提供瞭如下方法建立流。數據結構

  1. of:
    Stream<Integer> stream = Stream.of(1, 2, 3, 4);
    IntStream intStream = IntStream.of(new int[]{1, 2, 3});
  2. generator:生成一個無限長度的Stream
    能夠看做是個工廠,每次返回一個給定類型的對象。
    Stream<Double> generate = Stream.generate(Math::random);
  3. iterate:生成無限長度的Stream
    其元素的生成是重複對給定的種子值(seed)調用用戶指定函數生成的
    Stream<Integer> stream = Stream.iterate(1, element -> element + 1);
  4. empty():生成空流
    Stream.empty();
  5. 經過文件生成流:File.lines()
    Stream<String> lines = Files.lines(Paths.get("data.txt"));
  6. 最經常使用的 經過Collection子類獲取Stream
    List<Integer> list = new ArrayList<>();
    Stream<Integer> stream = list.stream();
  7. 經過Arrays.stream()封裝數據生成流,也很經常使用
    IntStream intStream = Arrays.stream(new int[]{1, 2, 3});
  8. 比較少用的 CharSequence 接口方法 .chars()
    String str = "hello world";
    IntStream intStream = str.chars();

描述流

建立好流以後,就能夠描述流了。更準確的說法是,聲明流的中間操做。
中間操做比較經常使用的有如下幾種(返回值爲Stream的均爲中間操做)框架

  • .filter(man -> man.getAge() > 18)  過濾器
  • .map(Man::getName)       獲取字段,map操做能夠執行屢次,將數據轉爲咱們指望的
  • .mapToInt            轉爲數值流,這個和sum這些組合使用能夠省去裝箱成本
  • .limit(3)            截斷流,前三個
  • .distinct()            去重
  • .sorted()            排序
  • .skip(3)             丟棄前三個
  • .flatMap(Arrays::stream)     合併流

求值

與中間操做相對應的是終端操做,也就是咱們的第三步,求值。
經常使用的終端操做有如下幾種(返回值不爲Stream的爲終端操做)dom

  • .collect(toList());    結果轉爲List格式
  • .collect(toSet());    結構轉爲Set格式,自帶distinct效果
  • .count();       計數
  • .findfirst();       找第一個
  • .findAny();      找任何一個
  • .forEach(System.out::println) 爲每一個元素進行的操做
  • .min         獲取最小值
  • .max         獲取最大值

說明

流是使用了內部迭代而不是外部迭代
內部迭代使用了惰性求值,只有在須要結果的那一刻才進行求值。
全部的中間操做(只描述流)都是惰性求值,由於只返回了Stream。

三.例子

// 用戶類函數

public class User {
    private Integer id;
    private String name;
    private Integer age;
    // GET SET Constructor ..
}

// 初始化數據調試

List<User> userList = new ArrayList<User>() {
    /** */
    private static final long serialVersionUID = 1L;
    {
        add(new User(1, "Luffy",   17)); 
        add(new User(2, "Zoro",    19)); 
        add(new User(3, "Nami",    18)); 
        add(new User(4, "Usopp",   17)); 
        add(new User(5, "Sanji",   19)); 
        add(new User(6, "Chopper", 15));
        add(new User(7, "Robin",   28)); 
        add(new User(8, "FRANKY",  34)); 
        add(new User(9, "BROOK",   88)); 
    }
};

// 例子code

@Test
public void test1() {
    // 獲取全部年齡大於17的用戶
    List<User> users = userList.stream()
            .filter(user -> user.getAge() > 17)
            .collect(Collectors.toList());
    System.out.println(users);
}

// 靜態導入Collectors後會更簡潔。
// import static java.util.stream.Collectors.*;

@Test
public void test2() {
    // 根據年齡是否大於17歲對集合進行分塊
    Map<Boolean, List<User>> users = userList.stream()
        .collect(partitioningBy(u -> u.getAge() > 17));
    System.out.println(users);
}

@Test
public void test3() {
    // 按照年齡,將用戶分組
    Map<Integer, List<User>> users = userList.stream()
        .collect(groupingBy(User::getAge));
    System.out.println(users);
}

@Test
public void test4() {
    // 獲取全部用戶的名稱
    List<String> names = userList.stream()
        .map(User::getName)
        .collect(toList());
    System.out.println(names);
}

@Test
public void test5() {
    // 獲取年齡最大的用戶(只返回一個)
    User user = userList.stream()
        .max((u1, u2) -> {
            return u1.getAge() > u2.getAge() ? 1 : -1;
        })
        .get();
    System.out.println(user);
}

@Test
public void test6() {
    // 獲取用戶年齡總和
    int ageSum = userList.stream()
        .map(User::getAge)
        .reduce(0, (acc, element) -> {
            acc += element;
            return acc;
        });
    System.out.println(ageSum);

    ageSum = userList.stream()
        .map(User::getAge)
        .reduce(0, Integer::sum);
    System.out.println(ageSum);

    ageSum = userList.stream()
        .collect(summingInt(User::getAge));
    System.out.println(ageSum);
}

@Test
public void test7() {
    // 獲取組成用戶姓名的字母
    Set<String> set = userList.stream()
        .map(User::getName)
        .map(name -> name.split(""))
        .flatMap(Arrays::stream)
        .collect(toSet());
    System.out.println(set);
}

四.注意點

如何進行調試對象

// 能夠利用peek查看流中的數據狀態
userList.stream()
    .peek(System.out::println)
    .map(u -> u.getName())
    .peek(System.out::println)
    .collect(toList());

使用並行仍是串行排序

// 將流改成並行流十分簡單,只須要將stream方法改成parallelStream
Map<Integer, List<User>> users = userList.parallelStream()                
    .collect(groupingBy(User::getAge));

// 或是使用 parallel() 聲明爲並行
Stream.iterate(0L, i -> i + 1)
    .parallel()
    .limit(10)
    .reduce(0L, Long::sum);

Java8流的並行屬於數據並行,即將數據分爲幾部分,利用多核CPU對每塊數據執行運算,最終彙總數據,獲得答案。底層是使用了fork/join框架,fork遞歸分解問題,而後每段並行執行,最終join合併結果。 所以,使用並行流須要考慮如下幾點:

  1. 數據量 : 將問題分解以後並行化處理,再將結果合併會帶來額外的開銷。
    所以數據足夠大,每一個數據處理管道花費的時間足夠多時,並行化處理纔有意義
  2. 將數據對半分解的難易程度 :
    ArrayList、數組、IntStream.range,這些數據結構支持隨機讀取,也就是說能垂手可得地被任意分解。
    LinkedList,對半分解太難。須要o(n) 時間複雜度
  3. 核的數量 : 單核的機器是不必並行化的
相關文章
相關標籤/搜索