Stream

简介

流(Stream)提供了数据视图,让你可以在笔集合类更高的概念层上制定操作,使用流,只需要指定做什么,而不是则么做。你只需要将操作的调度执行留给实现。

从迭代到 Stream 操作

例子统计一个文件中的长单词(在Android Studio中实现):

  • 读取 raw 文件中的 txt 文件:

    public static String readTextFileFromResource(Context context, int ResId){
          StringBuilder body = new StringBuilder();
    
          InputStream inputStream = context.getResources().openRawResource(ResId);
          InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
          BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
          String nextLine;
          try {
              while ((nextLine = bufferedReader.readLine()) != null){
                  body.append(nextLine);
              }
          } catch (IOException e) {
              e.printStackTrace();
          }
    
          return body.toString();
      }
    
  • 普通迭代来统计:video_sequences.txt 文件有 76KB

    String contents = TextResReader.readTextFileFromResource(this, R.raw.video_sequences);
          Log.d("contents", contents);
      //用正则将字符串拆分成单词
          List words = Arrays.asList(contents.split("[^a-zA-z0-9]"));
          int count = 0;
          long startTime = System.currentTimeMillis();
          for (String w : words) {
              if (w.length() > 12)
                  count++;
          }
          Log.d("Count", count + "");
          long endTime = System.currentTimeMillis(); //获取结束时间
          System.out.println("普通程序运行时间: " + (endTime - startTime) + "ms");
    
  • 使用流,实现相同的功能:

    long startTime1 = System.currentTimeMillis();
          long counts = Stream.of(words).filter(w -> w.length() > 12).count();
          Log.d("StreamCount", counts + "");
          long endTime1 = System.currentTimeMillis(); //获取结束时间
          System.out.println("Stream程序运行时间: " + (endTime1 - startTime1) + "ms");
    

打印结果:

D/Count: 183
I/System.out: 普通程序运行时间: 64ms
D/StreamCount: 183
I/System.out: Stream程序运行时间: 30ms

只使用了一行代码就能搞定,不用为了过滤和扫描循环了,效率也提高了不少。 Stream 遵循做什么,而不是怎么去做的原则。在这个实例中,我们描述了需要做的事情,找到单词并对他们计数,我们不需要指定顺序,不需要指定在哪个线程上运行,执行顺序和执行线程都会自动由 Stream 实现。

流表面上看起来与集合类似,允许你转换和检索数据,但是他们是不同的:

  • 流不存储元素。它们存储在底层的集合或者按需生成。
  • 流操作不改变源数据。filter方法不会从一个新流中删除元素,而是生成一个不包含特定元素的新流。
  • 如果可能的话Stream操作可能是延迟执行的。

使用流的流程

  1. 创建一个Stream
  2. 指定将初始流转换成其他流的中间操作,可能需要多步操作。
  3. 应用终止操纵产生结果,在这之后流就不会用到了。

创建 Stream

  • 可以使用 Collection 接口的 stream 方法将任何集合转化为 Stream
  • 如果面对的是数组,则使用静态方法 Stream.of()方法将它转化为一个 stream

一些创建方法:

//1.Stream.of()
//split 方法返回 String[] 数组
Stream wordsStream = Stream.of(contents.split("[^a-zA-z0-9]"));
 //of方法可以接受可变长度的参数
Stream song = Stream.of("gently", "down", "the", "stream");

//2.Array.stream(array, from, to) 将数组的一部分转化成 Stream

//3.创建一个不含任何元素的 Stream
Stream silence = Stream.empty();

// Stream 接口有两个创建无限 Stream 的方法 generate()、iterate()
//4.generate()方法
//参数是一个 Supplier 的对象 下面创建一个常量值的 Stream
Stream echos = Stream.generate(() -> "echo");
//一个含有随机数的 Stream
Stream randoms = Stream.generate(Math::random);
//5.iterate()方法
//创建一个如 0 1 2 3 ... 的无穷数列,可以使用iterate()方法
//第一个参数种子值,第二个是一个函数
Stream integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));

filter、map 和 flatMap 方法

流的转换产生了一个流,该流的元素来源于其他流。 filter 转换生成一个匹配条件的新流。如:

//长单词流
//filter 的参数是一个 Predicate<T> 对象
Stream<String> longWords = Stream.of(words).filter(w -> w.length() > 12);

将流中的值进行某种形式的转换(map方法)

//将所有单词转换成小写形式:
//使用方法引用
Stream<String> lowercaseWords = Stream.of(words).map(String::toLowerCase);
//使用 lambda 表达式,产生一个包含每个单词的第一个字符的流
Stream<String> firstLetters = Stream.of(words).map(s -> s.substring(0, 1));

当使用 map 方法时,函数将作用于每一个元素,这样就产生了一个包含最终结果的新流。假设有一个函数,返回的不是一个值,而是一个包含多个值的流,如:

public Stream letters(String s){
        List result = new ArrayList<>();
        for (int i = 0; i < s.length(); i++){
            result.add(s.substring(i, i + 1));
        }
        return Stream.of(result);
    }

letters(“boat”) 返回的是流 [“b”, “o”, “a”, “t”]。

将该 letters() 方法映射到一个字符串流:

Stream> result = Stream.of(words).map(w -> letters(w));

结果是:

[…[“b”,”o”,”a”,”t”],[“y”,”o”,”u”],…], 要将其展开为一个只包含字符串的流 […”b”,”o”,”a”,”t”,”y”,”o”,”u”,…]使用 flatMap 方法而不是 map 方法:

Stream<String> flatResult = Stream.of(words).flatMap(w -> letters(w));

提取子流和组合流

  • stream.limit(n) 会返回一个包含 n 个元素的新流(如果原始流的长度小于n,则会返回原始流),这个方法特别适用于裁剪指定长度的流。例如:

    //产生一个10个随机数的流
    Stream<Double> randomsDouble = Stream.generate(Math::random).limit(10);
    
  • stream.skip(n) 正好相反,它会丢弃前 n 个元素。

    Stream<String> randomsSkip = Stream.of("gently", "down", "the", "stream").skip(1);
    
  • concat 连接流,第一个流不应该是无限的

    Stream<String> combined = Stream.concat(letters("hello"), letters("world"));
    //生成 combined: ["h", "e", "l", "l", "o", "w", "o", "r", "l", "d"]
    

其他流转换

  • distinct方法会根据原始流中的元素返回一个具有相同顺序、抑制了重复元素的新流。

    Stream<String> uniqueWords = Stream.of("gently", "down","down", "the", "stream").distinct();
    
  • Stream排序

简单归约

归约是流的终止操作,也就是从流中获得答案 。