java 8 stream

@java 8 stream

java 8 stream

本篇文章主要讲如何使用Java8 的流库

流的概念

流表面上和集合很类似,都可以让我们转换和获取数据。但是,它们之间存在着显著的差异

  1. 流并不存储其元素。这些元素可能存储在底层的集合中,或者是按需生成的。
  2. 流的操作不会修改其数据源。比如,filter方法不会从原来的流中移除数据,而是生成一个不包含被过滤掉的元素的新的流,
  3. 流的操作是尽可能惰性执行的。这意味着直至需要其结果时,操作才会执行;如下面的代码如果我们只想查找前5个长度大于5的单词而不是所有,那么filter方法就会在匹配搭配第5个单词后停止过滤。
public static void main(String[] args) {
        String words = "word,main,args,static,smile,happy,high,low,good,bad" ;

        List<String> list = Arrays.asList(words.split(","));
        long count = 0;
        for(int i =0;i<list.size();i++){
            if(list.get(i).length()>5){
                count ++;
            }
        }
        System.out.println(count);
        count =list.stream().filter(s -> s.length() > 3).count();
        System.out.println(count);
        count =list.parallelStream().filter(s -> s.length() > 3).count();
        System.out.println(count);
    }

filter 产生一个流,其中包含满足其条件的所有元素
count 产生当前流中元素的数量。
stream 和 parallelStream 的区别就是parallelStream是并行流,stream 是顺序流。
让我们在看看上面的代码,stream方法会产生一个用于list的stream,filter会产生另一个流,其中只包含长度大于3的单词,count方法将这个流化简为一个结果。
这个工作流是操作流时的典型流程,我们建立了一个包含三个阶段的操作管道:
1.创建一个流。
2.指定将初始流转换为其他流的中间操作,可能包含多个步骤。
3.应用终止操作,从而产生结果。这个操作会强制执行之前的惰性操作。之后这个流就不能用了。

创建流的方式(概括)

1.Collection.stream()
Collection.parallelStream()
2.Stream.of() 产生一个元素为给定值的流
3.Stream.empty() 产生一个不包含任何元素的流
4.Stream.generate(Suppiler s)
通过实现 Supplier 接口,你可以自己来控制流的生成。这种情形通常用于随机数、常量的 Stream,或者需要前后元素间维持着某种状态信息的 Stream。把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。由于它是无限的,在管道中,必须利用 limit 之类的操作限制 Stream 大小。
例 生成十个随机数

 Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);
 

Stream.generate() 还接受自己实现的 Supplier。例如在构造海量测试数据的时候,用某种自动的规则给每一个变量赋值;或者依据公式计算 Stream 的每个元素值。这些都是维持状态信息的情形

Stream.generate(new PersonSupplier()).
limit(10).
forEach(p -> System.out.println(p.getName() + ", " + p.getAge()));
private class PersonSupplier implements Supplier<Person> {
 private int index = 0;
 private Random random = new Random();
 @Override
 public Person get() {
 return new Person(index++, "StormTestUser" + index, random.nextInt(100));
 }
}

输出结果
StormTestUser1, 9
StormTestUser2, 12
StormTestUser3, 88
StormTestUser4, 51
StormTestUser5, 22
StormTestUser6, 28
StormTestUser7, 81
StormTestUser8, 51
StormTestUser9, 4
StormTestUser10, 76

Stream.iterate(T seed ,UnaryOperator f)
产生一个无限流,它的元素包含种子,在种子上调用f产生的值、在前一个元素上调用f产生的值,然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,以此类推。

 例 生成一个等差数列
 Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.out.print(x + " "));
 结果 0 3 6 9 12 15 18 21 24 27

与 Stream.generate 相仿,在 iterate 时候管道必须有 limit 这样的操作来限制 Stream 大小。

流的操作

接下来,当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。

intermediate 中间操作

该操作会保持 stream 处于中间状态,允许做进一步的操作。它返回的还是的 Stream,允许更多的链式操作。常见的中间操作有:
filter():对元素进行过滤;
sorted():对元素排序;
map():元素的映射;
distinct():去除重复元素;
limit(): 截取元素;
skip (n):当前流中跳过前n个元素之外的所有元素;
concat():连接两个流的元素;

Terminal 终止操作

该操作必须是流的最后一个操作,一旦被调用,Stream 就到了一个终止状态,而且不能再使用了。常见的终止操作有:
max:最大元素;
min:最小元素;
findFirst():第一个元素;
findAny():任意一个元素;
anyMatch,allMatch,noneMatch:分别在这个流中任意元素、所有元素和没有任何元素匹配给定断言是返回true。

并行流

流使得并行处理块的操作更容易。这个过程几乎是自动饿的,但是需要遵守一些规则。首先必须有一个并行流。可以用Collection.parallelStream方法从任何集合中获取一个并行流:

Stream<String> parallelList = list.parallelStream();

而且,parallel 方法可以将任意的顺序流转换为并行流

Stream<String> paralleList = Stream.of(list).parallel();

只要在终结方法执行时,流处于并行模式,那么所有的中间流操作都将被并行化。当流操作并行运行时,其目标是要让其返回结果与顺序执行时返回的结果相同,重要的是,这些操作可以按任意顺序执行。
下面是一个示例,假设你想要对字符串流中的所有短单词计数

String word = "word,main,args,static,smile,happy,high,low,good,bad" ;
        List<String> wordList = Arrays.asList(word.split(","));
        int[] shortWords = new int[12];
        wordList.parallelStream().forEach(
                s -> {if(s.length() <5) {
                    shortWords[s.length()]++;
                }}
        );
        System.out.println(Arrays.toString(shortWords));

按上面的方法执行,你会发现每次运行产生的结果都不一样,而且每个数都是错的,这是因为传递给forEach 的函数会在多个并发线程中运行,每个都会更新共享的数组。但是如果我们如果用长度将字符串分组,然后分别对它们进行计数,那么就可以安全的并行化

Map<Integer, Long> collect = wordList.parallelStream().filter(s -> s.length() < 5)
                .collect(Collectors.groupingBy(String::length, Collectors.counting()));
        System.out.println(collect);

当放弃排序需求时,有些操作可以被更有效的并行化。通过在流上调用unordered方法就可以明确表示我们对排序不敢兴趣。distinct就是可以应用的一种操作。在有序的流中,distinct 会保留所有相同元素中的第一个,这对并行化是一种阻碍,因为处理每个部分的线程在之前的所有部分被处理完之前,并不知道应该丢弃那些元素。如果可以接受保留唯一元素中的任意一个的做法,那么所有部分就可以并行的处理。
还可以通过放弃排序要求来提高limit 方法的速度。如果只想从流中取出任意n个元素,而并不在意到底要获取那些,那么可以调用:

        Stream<String> limit = wordList.parallelStream().unordered().limit(3);

当然 并不是所有的都需要并行流,当对内存中的数据执行大量计算操作是,才应该使用并行流。否则没有什么意思。

总结

总之,Stream 的特性可以归纳为:

  1. 不是数据结构
  2. 它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据。
  3. 它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素。
  4. 所有 Stream 的操作必须以 lambda 表达式为参数
  5. 不支持索引访问;你可以请求第一个元素,但无法请求第二个,第三个,或最后一个。不过请参阅下一项。
  6. 很容易生成数组或者 List
  7. 惰性化;很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。Intermediate 操作永远是惰性化的。
  8. 并行能力;当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。
  9. 可以是无限的;集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的操作可以对无限的 Stream 进行运算并很快完成。

Alt

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值