java1.8--流

 几乎每个Java应用都会制造和处理集合。但集合用起来并不总是那么理想。比方说,你需要 从一个列表中筛选金额较高的交易,然后按货币分组。你需要写一大堆套路化的代码来实现这个 数据处理命令,如下所示:

Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();
    for(Transaction transaction :transactions){
        if (transaction.getPrice() > 1000) {
            Currency currency = transaction.getCurrency();
            List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);
            if (transactionsForCurrency == null) {
                transactionsForCurrency = new ArrayList<>();
                transactionsByCurrencies.put(currency, transactionsForCurrency);
            }
            transactionsForCurrency.add(transaction);
        }
    }

此外,我们很难一眼看出来这些代码是做什么的,因为有好几个嵌套的控制流指令。 有了Stream API,你现在可以这样解决这个问题了:

            Map<Currency, List<Transaction>> transactionsByCurrencies =
            transactions.stream()
                    .filter((Transaction t) -> t.getPrice() > 1000)
                    .collect(groupingBy(Transaction::getCurrency));

和Collection API相比,Stream API处理数据的方式非常不同。用集合的话,你得 自己去做迭代的过程。你得用for-each循环一个个去迭代元素,然后再处理元素。我们把这种 数据迭代的方法称为外部迭代。相反,有了Stream API,你根本用不着操心循环的事情。数据处 理完全是在库内部进行的。我们把这种思想叫作内部迭代。

使用集合的另一个头疼的地方是,想想看,要是你的交易量非常庞大,你要怎么处理这个巨 大的列表呢?单个CPU根本搞不定这么大量的数据,但你很可能已经有了一台多核电脑。理想的 情况下,你可能想让这些CPU内核共同分担处理工作,以缩短处理时间。理论上来说,要是你有 八个核,那并行起来,处理数据的速度应该是单核的八倍。

多核

所有新的台式和笔记本电脑都是多核的。它们不是仅有一个CPU,而是有四个、八个,甚 至更多CPU,通常称为内核①。问题是,经典的Java程序只能利用其中一个核,其他核的处理 能力都浪费了。类似地,很多公司利用计算集群(用高速网络连接起来的多台计算机)来高效 处理海量数据。Java 8提供了新的编程风格,可更好地利用这样的计算机。 Google的搜索引擎就是一个无法在单台计算机上运行的代码的例子。它要读取互联网上 的每个页面并建立索引,将每个互联网网页上出现的每个词都映射到包含该词的网址上。然 后,如果你用多个单词进行搜索,软件就可以快速利用索引,给你一个包含这些词的网页集 合。想想看,你会如何在Java中实现这个算法,哪怕是比Google小的引擎也需要你利用计算 机上所有的核。

多线程并非易事

问题在于,通过多线程代码来利用并行(使用先前Java版本中的Thread API)并非易事。你 得换一种思路:线程可能会同时访问并更新共享变量。因此,如果没有协调好②,数据可能会被 意外改变。相比一步步执行的顺序模型,这个模型不太好理解③。比如,图1-5就展示了如果没有 同步好,两个线程同时向共享变量sum加上一个数时,可能出现的问题。 

Java 8也用Stream API(java.util.stream)解决了这两个问题:集合处理时的套路和晦 涩,以及难以利用多核。这样设计的第一个原因是,有许多反复出现的数据处理模式,类似于前 一节所说的filterApples或SQL等数据库查询语言里熟悉的操作,如果在库中有这些就会很方 便:根据标准筛选数据(比如较重的苹果),提取数据(例如抽取列表中每个苹果的重量字段), 或给数据分组(例如,将一个数字列表分组,奇数和偶数分别列表)等。第二个原因是,这类操 作常常可以并行化。例如,如图1-6所示,在两个CPU上筛选列表,可以让一个CPU处理列表的 前一半,第二个CPU处理后一半,这称为分支步骤(1)。CPU随后对各自的半个列表做筛选(2)。 最后(3),一个CPU会把两个结果合并起来(Google搜索这么快就与此紧密相关,当然他们用的 CPU远远不止两个了)。

到这里,我们只是说新的Stream API和Java现有的集合API的行为差不多:它们都能够访问数 据项目的序列。不过,现在最好记得,Collection主要是为了存储和访问数据,而Stream则主要用 于描述对数据的计算。这里的关键点在于,Stream允许并提倡并行处理一个Stream中的元素。 虽然可能乍看上去有点儿怪,但筛选一个Collection(将上一节的filterApples应用在一个 List上)的最快方法常常是将其转换为Stream,进行并行处理,然后再转换回List,下面举的 串行和并行的例子都是如此。我们这里还只是说“几乎免费的并行”,让你稍微体验一下,如何 利用Stream和Lambda表达式顺序或并行地从一个列表里筛选比较重的苹果。

顺序处理:

            List<Apple> heavyApples =
            inventory.stream().filter((Apple a) -> a.getWeight() > 150)
                    .collect(toList());

并行处理:

            List<Apple> heavyApples =
            inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150)
                    .collect(toList());

Java中的并行与无共享可变状态

大家都说Java里面并行很难,而且和synchronized相关的玩意儿都容易出问题。那Java 8 里面有什么“灵丹妙药”呢?事实上有两个。首先,库会负责分块,即把大的流分成几个小的 流,以便并行处理。其次,流提供的这个几乎免费的并行,只有在传递给filter之类的库方 法的方法不会互动(比方说有可变的共享对象)时才能工作。但是其实这个限制对于程序员来 说挺自然的,举个例子,我们的Apple::isGreenApple就是这样。确实,虽然函数式编程中 的函数的主要意思是“把函数作为一等值”,不过它也常常隐含着第二层意思,即“执行时在 元素之间无互动”。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值