`
deepinmind
  • 浏览: 451496 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
1dc14e59-7bdf-33ab-841a-02d087aed982
Java函数式编程
浏览量:41626
社区版块
存档分类
最新评论

Java函数式编程(九)MapReduce

阅读更多
译注:map(映射)和reduce(归约,化简)是数学上两个很基础的概念,它们很早就出现在各类的函数编程语言里了,直到2003年Google将其发扬光大,运用到分布式系统中进行并行计算后,这个组合的名字才开始在计算机界大放异彩(那些函数式粉可能并不这么认为)。本文我们会看到Java 8在摇身一变支持函数式编程后,map和reduce组合的首次亮相(这里只是初步介绍,后续还会有针对它们的专题)。


本系列文章译自Venkat Subramaniam的Functional Programming in Java

未完待续,后续文章请继续关注Java译站


对集合进行归约

现在为止我们已经介绍了几个操作集合的新技巧了:查找匹配元素,查找单个元素,集合转化。这些操作有一个共同点,它们都是对集合中的单个元素进行操作。不需要对元素进行比较,或者对两个元素进行运算。本节中我们来看一下如何比较元素,以及在遍历集合过程中动态维护一个运算结果。

我们先从简单的例子开始,然后再循序渐进。在第一个例子中,我们先来遍历一下friends集合,计算出所有名字的总字符数。

System.out.println("Total number of characters in all names: " + friends.stream()
         .mapToInt(name -> name.length())
         .sum());


要算出所有字符的总数我们得知道每个名字的长度。通过mapToInt()方法可以轻松的完成这个。当我们已经把名字转化成了对应的长度之后,最后只需要把它们加到一块就行了。我们有一个内置的sum()方法来完成这个。下面是最后的输出:

Total number of characters in all names: 26 


我们使用了map操作的一个变种,mapToInt()方法(这种的有mapToInt, mapToDouble等,会对应生成具体类型的流,比如IntStream,DoubleStream),然后根据返回的长度计算出总的字符数。

除了使用sum方法,还有很多类似的方法可以使用,比如用max()可以求出最大的长度,用min()是最小长度,sorted()对长度进行排序,average()求平均长度,等等。

上述这个例子还有一个吸引人的地方就是现在越来越流行的MapReduce模式,map()方法进行映射,而sum()方法是一个比较常用的reduce操作。事实上,JDK中sum()方法的实现用的就是reduce()方法。我们来看下reduce操作更常用的一些形式。

比方说,我们遍历所有的名字,然后打印出名字最长的那个。如果最长的名字有好几个,我们就打印出最开始找到的那个。一种方法是,我们计算出最大的长度,然后选出匹配这个长度的第一个元素。不过这样做需要遍历两次列表——效率太低了。这正是reduce操作上场的时候了。

我们可以用reduce操作来比较两个元素的长度,然后返回最长的那个,再和剩下的元素做进一步比较。跟我们之前看到的别的高阶函数一样,reduce()方法同样也是遍历了整个集合。除此之外,它还记录了lambda表达式返回的计算结果。有个例子的话可以帮助我们更好的理解这点,那我们先来看一段代码吧。

final Optional<String> aLongName = friends.stream()
         .reduce((name1, name2) ->
            name1.length() >= name2.length() ? name1 : name2);
aLongName.ifPresent(name ->
System.out.println(String.format("A longest name: %s", name)));



传给reduce()方法的lambda表达式接收两个参数,name1和name2,它会比较它们的长度,返回最长的那个。reduce()方法根本不知道我们要干什么。这个逻辑被剥离到我们传递进去的lambda表达式里面了——这是策略模式的一个轻量级的实现。


这个lambda表达式正好能适配成JDK中一个BinaryOperator的函数式接口的apply方法。这正是reduce方法要接受的参数类型。我们来运行下这个reduce方法,看看它能否正确地在两个最长的名字中选出第一个来。

A longest name: Brian


在reduce()方法遍历集合的过程中,它先对集合的前两个元素调用了lambda表达式,调用返回的结果继续用于下一次调用。在第二次调用中,name1的值被绑定成上次调用的结果,name2的值则是集合的第三个元素。剩余的元素也这样依次调用下去。最后一次lambda表达式调用的结果,就是整个reduce()方法返回的结果。

reduce()方法返回的是一个Optional值,因为传递给它的集合可能是空的。那样的话,也不存在什么最长的名字了。如果列表只有一个元素,reduce方法直接返回那个元素,不会对lambda表达式进行调用。

从这个例子中我们可以推断出,reduce的结果最多只可能是集合中的一个元素。如果我们希望能返回一个默认值或者基础值的话,我们可以使用reduce()方法的一个变种,它可以接收一个额外的参数。比如,如果最短的名字是Steve,我们可以把它传给reduce()方法,像这样:

final String steveOrLonger = friends.stream()
     .reduce("Steve", (name1, name2) ->
            name1.length() >= name2.length() ? name1 : name2);


如果有名字比它长的,那么这个名字会被选中;否则的话就返回这个基础值Steve。这个版本的reduce()方法不会返回Optional对象,因为如果集合是空的,会返回一个默认值;不用考虑没有返回值的情况。

在我们结束这章之前 ,我们再来看一下集合操作里面一个很基础的却又不是那么容易的操作:合并元素。

合并元素

我们已经学习了如何进行元素的查找,遍历,以及集合的转化。不过还有一个常见的操作——将集合元素进行拼接——如果没有这个新添加的join()函数的话,之前说的简洁和优雅的代码只能成为泡影了。这个简单的方法非常实用以至于它成为JDK里最常用的函数之一。我们来看下如何用它来打印列表中的元素,用逗号进行分隔。

我们还是用这个friends列表。如果用JDK库里的旧方法的话,想要打印出所有名字并用逗号隔开的话,要做哪些工作?

我们得遍历列表并且挨个打印元素。Java 5中的for循环比之前的有所改进,我们就用它吧。



for(String name : friends) {
      System.out.print(name + ", ");
}
System.out.println();


代码很简单,我们看下它的输出是什么。

Brian, Nate, Neal, Raju, Sara, Scott,


该死,最后多出了一个讨厌的逗号(我们难道要怪最后的那个Scott?)。怎么能让Java别放一个逗号在这呢?不幸的是,循环会按步就班的执行,想让它在最后特殊处理一下可不容易。为了解决这个问题,我们可以用回原来的那种循环方式。

for(int i = 0; i < friends.size() - 1; i++) {
      System.out.print(friends.get(i) + ", ");
}
if(friends.size() > 0)
      System.out.println(friends.get(friends.size() - 1));


我们来看下这个版本的输出是不是OK。

Brian, Nate, Neal, Raju, Sara, Scott


结果还是不错的,不过这个代码就不敢恭维了。救救我们吧,Java。

我们不用再忍受这种痛苦了。Java 8里的StringJoiner类帮我们搞定了这些难题,不止如此,String类还增加了一个join方法方便我们可以用一行代码来替代掉上面那坨东西。

System.out.println(String.join(", ", friends));


快来看下吧,结果跟代码一样令人满意。

Brian, Nate, Neal, Raju, Sara, Scott


在底层实现中,String.join()方法调用了StringJoiner类来将第二个参数传进来的值(这是个变长参数)拼接成一个长的字符串,用第一个参数作为分隔符。这个方法当然不止是能拼接逗号这么简单了。比如说,我们可以传入一堆路径,然后很容易的拼出一个类路径(classpath),这可真是多亏了这些新增加的方法和类。

我们已经知道如何去连接列表元素了,在进行列表连接前,我们还可以先对元素进行转化,当然我们也知道如何使用map方法来进行列表转化了。接下来还可以用filter()方法过滤出我们想要的那些元素。最后一步的连接列表元素,用逗号还是什么分隔符,不过就是一个简单的reduce操作而已了。

我们可以用reduce()方法将元素拼接成一个字符串,不过这需要我们费点工夫。JDK有一个十分方便的collect()方法,它也是reduce()的一个变种,我们可以用它来把元素合并成一个想要的值。

collect()方法来执行归约操作,不过它把具体的操作委托给一个collector来执行。我们可以把转化后的元素合并成一个ArrayList。继续刚才那个例子,我们可以将转化后的元素,拼接成一个用逗号分隔的字符串。

System.out.println(
      friends.stream()
          .map(String::toUpperCase)
          .collect(joining(", ")));


我们在转化后的列表上调用了collect()方法,给它传入了一个joining()方法返回的collector,joining是Collectors工具类里的一个静态方法。collector就像是个接收器,它接收collect传进来的对象,并把它们存储成你想要的格式:ArrayList, String等。我们会在52页的collect方法及Collectors类中进一步探索这个方法。

这是输出的名字,现在它们是大写的,并用逗号隔开。

BRIAN, NATE, NEAL, RAJU, SARA, SCOTT


StringJoiner类让我们可以更灵活的控制集合连接的格式;我们可以指定前缀,后缀,或者中缀,怎样都行。

lambda表达式和新添加的方法及类让Java编程更得更加简单,惬意。我们来回顾下这章都讲了些什么。

总结

集合在编程中十分常见,有了lambda表达式后,Java的集合操作变得更加简单容易了。那些拖沓的集合操作的老代码都可以换成这种优雅简洁的新方式。内部迭代器使得集合遍历,转化都变得更加方便,远离了可变性的烦恼,查找集合元素也变得异常轻松。使用这些新方法可以少写不少代码。这使得代码更容易维护,更聚焦于业务逻辑,编程中的那些基本操作也变得更少了。

下一章中我们会看到lambda表达式如何简化程序开发中的另一个基本操作:字符串操作以及对象比较。


未完待续,后续文章请继续关注Java译站


原创文章转载请注明出处:http://it.deepinmind.com



5
6
分享到:
评论

相关推荐

    读书笔记:用java模拟scala函数式编程模拟实现mapreduce.zip

    读书笔记:用java模拟scala函数式编程模拟实现mapreduce

    Java函数式编程(七):MapReduce

    Java函数式编程中的MapReduce是一种将复杂计算分解为简单映射和归约操作的编程范式,尤其在处理大量数据时表现出高效性。这个概念起源于Google的分布式计算框架,后来被广泛应用于各种编程语言,包括Java。在Java 8...

    MapReduce多语言编程上

    这个编程模型借鉴了函数式编程中的“映射”(Map)和“归约”(Reduce)概念,以高效且可扩展的方式处理分布式计算任务。 1. **Map阶段**: 在MapReduce中,数据首先被分割成多个小块,这些数据块被分发到集群中的...

    Spark从入门到精通Java版

    课程主要涉及Spark的基础知识、Scala编程语言、函数式编程、面向对象编程、模式匹配、类型参数、隐式转换与隐式参数、Actor编程等方面。 Spark基础知识 Spark是什么?Spark是一个基于内存的数据处理引擎,主要用于...

    Hadoop-MapReduce实践示例

    1. MapReduce的设计与工作原理:MapReduce的设计思想源自于函数式编程中的map和reduce操作。Map操作负责将输入数据集分割成独立的元素,然后进行处理(如排序、筛选等),产生中间结果。Reduce操作则将具有相同键的...

    基于Java的Hadoop核心功能实现。包括HDFS及MapReduce等.zip

    它借鉴了函数式编程的Map和Reduce概念,将复杂的数据处理任务分解为一系列可并行执行的小任务。Map阶段将输入数据拆分成键值对,然后在各个节点上并行处理;Reduce阶段则负责汇总Map阶段的结果,完成聚合计算。...

    java官方源码-the-real-functional-[removed]使用JavaScript进行函数式编程的官方源代码

    java官方源码JavaScript中真正的函数式编程。 这是我的课程“ JavaScript中的实际功能编程”的官方资源库。 在此处免费在线阅读示例,或从以下网站购买: 该存储库将作为课程学习的指南,并在完成后作为您的参考,...

    HBase MapReduce完整实例.rar

    2. **代码示例**:提供实际的Java代码,演示如何编写Map和Reduce函数来处理HBase中的数据,以及如何使用HBaseInputFormat和HBaseOutputFormat与HBase交互。 3. **数据准备**:可能包含一些模拟数据或实际数据,用于...

    通过实例让你真正明白mapreduce填空式、分布(分割)编程

    #### 二、MapReduce填空式编程 MapReduce被称作“填空式”编程,主要是因为它提供了一个框架,用户只需要实现特定的接口或方法即可。具体来说,MapReduce框架包括两个主要部分: 1. **Mapper**:负责对输入数据...

    MapReduce统计手机上网流量

    标题中的“MapReduce统计手机上网流量”指的...总的来说,这个MapReduce项目涉及到大数据处理、分布式计算、Hadoop生态系统和Java编程,对于理解如何利用MapReduce解决实际问题,以及提升大数据分析能力具有重要意义。

    可视化毕业设计:数据处理(MapReduce)+数据展示(hbase+java+echarts).zip

    在本项目中,开发者会使用Java编写MapReduce作业,定义Map和Reduce函数,实现数据处理逻辑。同时,Java也是HBase的客户端API,用于与HBase交互,如插入、查询和更新数据。掌握Java编程技能对于理解并实施这个项目至...

    Hadoop_MapReduce教程

    - **不适合低延迟交互式查询**:由于 MapReduce 的工作方式,不适合需要低延迟响应的应用场景。 - **不适合流式数据处理**:虽然可以通过 Hadoop Streaming 实现流式处理,但这不是其主要应用场景。 - **资源占用...

    appengine-mapreduce-src-20110122.jar.zip

    它主要由两部分组成:Map阶段和Reduce阶段,灵感来源于函数式编程中的映射和化简操作。 描述中同样提到了"appengine-mapreduce-src-20110122.jar.zip",这意味着压缩包内容是源代码,而不是编译后的二进制文件。这...

    scala写的第一个wordcount例子

    Scala编写的WordCount程序是初学者接触这门函数式编程语言时常见的入门示例,它展示了如何处理文本数据并计算其中单词的出现次数。在这个例子中,我们主要涉及以下几个关键知识点: 1. Scala语言基础: Scala是一...

    分布式计算利器_MapReduce

    它的设计思想源自于函数式编程中的map和reduce操作。在Hadoop框架中,MapReduce作为一个核心组件,能够运行在上千台的商用机器上,以处理极其大量的数据。它允许开发者不必关心底层的集群运行和管理问题,只需通过...

    基于大数据背景Java编程语言创新研究.zip

    5. **Java 8及以上版本的新特性**:Java 8引入了Lambda表达式和Stream API,这些新特性极大地简化了函数式编程和数据处理,尤其适合大数据场景下的数据转换和过滤操作。 6. **Java大数据框架**:除了Hadoop和Spark...

    基于Hadoop的Java调用Matlab混合编程的车牌识别.zip

    2. **Java编程**: Java是实现Hadoop MapReduce任务的常用语言,其跨平台特性使得Java成为分布式计算环境的理想选择。在本项目中,Java可能被用来编写MapReduce作业,对车牌图片进行预处理、特征提取等操作。 3. **...

    pig java 编程jar包

    在Pig中,Java编程主要用于开发UDF(用户定义函数),这些函数可以是Java类,用于处理Pig无法直接处理的数据格式或业务逻辑。例如,如果你需要对数据进行特定的字符串处理或者应用复杂的数学计算,你可以编写一个...

    java大数据

    它的函数式编程特性使其在处理复杂数据流时更加高效。 9. **Java 8及以上版本**: 新的Java版本引入了lambda表达式、Stream API等特性,这些对于编写简洁的并发代码和处理大数据集合非常有帮助。 10. **数据可视化...

Global site tag (gtag.js) - Google Analytics