Spark大数据实时计算:基于Scala开发实战

978-7-115-59703-8
作者: 杨力
译者:
编辑: 张天怡
分类: Spark

图书目录:

详情

本书从大数据实时计算框架Spark的编程语言Scala入手,第1~4章重点介绍函数式编程语言Scala的基础语法、面向对象编程以及函数式编程等,再通过编程训练案例介绍Scala这门语言的实际开发应用,为读者后面学习Spark框架打下牢固的编程基础。第5~10章重点介绍Spark的安装部署、SparkCore编程、Spark SQL结构化数据处理以及Spark Streaming实时数据处理等,对它们进行详细的剖析和解读。最后,在第11章中通过网站运营指标统计和IP经纬度热力图分析两个实战开发项目,让读者充分掌握Spark大数据实时计算框架技术的应用与实操方法。 本书适合所有对大数据技术感兴趣的读者。

图书摘要

版权信息

书名:Spark大数据实时计算——基于Scala开发实战

ISBN:978-7-115-59703-8

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著    杨力

责任编辑 赵轩

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315

内容提要

本书从大数据实时计算框架Spark的编程语言 Scala 入手,第1~4 章重点介绍函数式编程语言Scala的基础语法、面向对象编程以及函数式编程等,再通过编程训练案例介绍Scala这门语言的实际开发应用,为读者后面学习Spark框架打下牢固的编程基础。第5~10 章重点介绍Spark的安装部署、SparkCore编程、Spark SQL结构化数据处理以及Spark Streaming实时数据处理等,对它们进行详细的剖析和解读。最后,在第11章中通过网站运营指标统计和IP经纬度热力图分析两个实战开发项目,让读者充分掌握Spark大数据实时计算框架技术的应用与实操方法。

本书适合所有对大数据技术感兴趣的读者。

前言

今天,大数据和人工智能正以前所未有的广度和深度影响着各行各业。现在及未来公司的核心壁垒就是数据,核心竞争力来自基于大数据的人工智能。Spark是当今大数据领域最活跃、最热门、最高效的大数据通用计算平台之一。

在任何规模的数据计算中,Spark在性能和扩展性上都具有优势。

Spark中的Spark SQL、Spark Streaming、MLlib、GraphX、R五大子框架和库之间可以无缝地共享数据和操作,这不仅打造了Spark在当今大数据计算领域其他计算框架都无可匹敌的优势,而且使Spark正在加速成为大数据处理中心首选的通用计算平台。

本书将带您了解Spark大数据实时计算的基本概念并进行实战操作。通过对本书的学习,您将对Spark大数据实时计算技术有深刻的认识,并且掌握大数据技术中主流的实时计算工具SparkRDD、Spark SQL、Spark Streaming等;再通过对大数据的实时计算项目案例开发的学习,您将了解Spark大数据实时计算技术的实际应用。学习本书是您掌握大数据实时计算技术非常好的入门途径。

作者在编写本书时力求内容科学准确、系统完整、通俗易懂,让初学者能快速掌握大数据技术,同时对专家级读者也具有一定的参考价值。希望通过本书对大数据技术的推广和传播,让大数据技术走进我们的生活、学习和工作中。

由于作者水平有限,书中难免出现疏漏,敬请读者批评指正。

致谢

感谢人民邮电出版社责任编辑赵轩,因为他的辛勤工作才让本书的出版成为可能。

感谢曾经和我一起奋战在“大数据一线”的孟老师、马老师、游老师、赵老师、李老师。

最后,特别感谢我的父亲、母亲、岳父、岳母及我的妻子,是他们的全力支持才使我能够顺利完成此书。

杨力

2022年7月

服务与支持

本书由异步社区出品,社区(https://www.epubit.com/)为您提供后续服务。

提交勘误信息

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,单击“提交勘误”,输入勘误信息,单击“提交”按钮即可,如下图所示。本书的作者和编辑会对您提交的勘误信息进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

与我们联系

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区投稿(直接访问www.epubit.com/ contribute即可)。

如果您所在的学校、培训机构或企业想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接通过邮件发送给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

关于异步社区和异步图书

“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT图书和电子书,以及高品质技术文章和视频课程。

更多详情请访问异步社区官网https://www.epubit.com。

“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、人工智能、测试、前端、网络技术等。

异步社区

微信服务号

第1章 Scala入门基础

Scala是面向对象编程和函数式编程这两种编程范式相结合的编程语言,也是一门静态类型的编程语言。Scala程序的执行需要编译和解释,其运行环境就是JVM(Java Virtual Machine,Java虚拟机)。Scala语言的静态类型特性有助于开发者避免在编写的复杂应用程序中出现漏洞(bug)。Scala的JVM运行环境和类JavaScript动态运行时特性,允许开发者去构建高性能应用系统,并且可以轻松访问巨大的Java类库和Scala库生态系统。

1.1 Scala语言的特色

Scala语言最初并没有引起开发者的重视,但随着“大数据时代”的到来,人们发现两个大数据处理框架Spark和Kafka竟都是用Scala语言开发出来的。至此,Scala这门“沉睡”已久的语言逐步进入大数据开发者的眼帘。

Scala语言表达能力强,能用少量代码实现需要用大量Java代码才能实现的功能。比如,我们要创建一个学生类,属性包括学生学号、学生姓名、学生年龄、班级信息。要实现这个功能,Java代码如下。

public class Student { //学生类
 private int studentNum;      //学生学号
 private String studentName;  //学生姓名
 private int studentAge;      //学生年龄
 private String classInfo;    //班级信息
 
    public int getStudentNum() {  
        return studentNum;  
    }  
  
    public void setStudentNum(int studentNum) {  
        this.studentNum = studentNum;  
    }  
  
    public String getStudentName() {  
        return studentName;  
    }  
  
    public void setStudentName(String studentName) {  
        this.studentName = studentName;  
    }  
  
    public int getStudentAge() {  
        return studentAge;  
    }  
  
    public void setStudentAge(int studentAge) {  
        this.studentAge = studentAge;  
    }  
  
    public String getClassInfo() {  
        return classInfo;  
    }  
  
    public void setClassInfo(String classInfo) {  
        this.classInfo = classInfo;  
    }  
  
    /** 
     * 重写toString 方法 
     * @return String 
     */  
    @Override  
    public String toString() {  
        return "Student{" +  
                "studentNum=" + studentNum +  
                ", studentName='" + studentName + '\'' +  
                ", studentAge=" + studentAge +  
                ", classInfo='" + classInfo + '\'' +  
                '}';  
    }  
}  

要实现与上面Java代码相同的功能,Scala代码如下。

case class Student(  var studentNum:int,  var studentName:String,  var studentAge:int,   var classInfo:String)   //学生类

可以看到,Scala代码更为简洁,可以极大地提高开发效率。此外,Scala兼容Java,可以访问庞大的Java类库。我们在Scala中调用Java程序的Scanner类并进行对话,代码如下。

scala> import java.util.Scanner  
import java.util.Scanner  
  
// 光标出现后输入 hello scala  
scala> println("hello world :" + new Scanner(System.in).nextLine())  
hello world :hello scala  

1.2 搭建Scala开发环境

Scala开发环境搭建分为两步,首先安装JDK(Java Development Kit,Java语言开发工具包),然后安装SDK(Scala Development Kit,Scala语言开发工具包)。

1.2.1 安装JDK

Scala程序的运行环境为JVM,其中经常会用到Java类库,所以我们在使用Scala前,一定要先安装JDK(1.8及以上版本),并配置好环境变量。

1.2.2 安装Scala SDK

安装好JDK后,还需要安装Scala SDK才可编译、运行Scala代码。在Scala官方网站下载适合的版本,如图1-1所示。

图1-1

以下是Scala SDK的安装流程(以Linux版本为例)。

① 在Scala官方网站下载名为scala-2.11.12.tgz的文件,并将其放到/opt/scala目录下。

② 进入/opt/scala目录,解压并安装scala-2.11.12.tgz文件,命令如下。

cd /opt/scala
tar -zxvf scala-2.11.12.tgz

③ 打开profile文件,配置环境变量,命令如下。

vi /etc/profile
export SCALA_HOME=/opt/scala/scala-2.11.12
export PATH=${SCALA_HOME}/bin:${PATH}

④ 保存并关闭profile文件,之后执行下面的命令,使profile文件重新生效。

source /etc/profile

⑤ 验证安装是否成功,命令如下。

scalac -version  

1.2.3 安装IDEA Scala插件

IDEA的全称是IntelliJ IDEA,是Java IDE(Integrated Development Environment,集成开发环境),但其默认不支持Scala语言,为此,我们需要下载和配置Scala插件。

① 登录IDEA官方网站,下载并安装最新版本的IDEA开发工具,如图1-2所示。

图1-2

② 在安装向导中选中Plugins,在搜索框中输入scala,单击Install按钮,如图1-3所示。

图1-3

如果在下载过程中遇到问题,也可以到IDEA官方网站下载Scala插件,如图1-4所示。

图1-4

③ 选中Scala选项并单击Get按钮后,可以看到Scala下载界面,其中第一列代表Scala版本,第二列代表IDEA版本。由于当前我们使用的IDEA为2021.1.3版本,因此下载2021.1.21版本的Scala插件,如图1-5所示。

图1-5

④ 将下载好的jar包通过图1-6所示的形式导入,重启IDEA即可。

图1-6

⑤ 如果出现图1-7所示的界面则表示安装成功,现在我们就可以在IDEA中编写Scala代码了。

图1-7

1.3 Scala解释器

Scala SDK中提供了Scala解释器,能帮助我们更方便地执行Scala程序。Scala 解释器经常用于代码测试。

1.3.1 启动Scala解释器

在命令行中执行scala命令,就可以启动Scala解释器,如图1-8所示。

图1-8

1.3.2 执行Scala代码

在命令行界面执行 print("hello world") 命令,如图1-9所示。

图1-9

1.3.3 退出Scala解释器

在命令行执行:quit命令或者按快捷键Ctrl+D即可退出Scala解释器。

1.4 Scala语法基础

想要熟练掌握Scala语言,不可忽视Scala语言语法的学习。

1.4.1 定义变量

Scala的变量是如何定义的呢?

比如,我们在Java中以“变量类型 变量名 = 初始值”的方式定义学生年龄。

int studentAge = 18; 

而在Scala中,我们需要通过不同的方式来确定变量是否不可变(只读):val/var变量名 : 变量类型 = 初始值。

变量类型可以指定,也可以不指定,由初始化的值决定。这种由初始化的值决定变量类型的方式叫作类型推断。var代表可变变量,val代表不可变变量。

使用定义格式定义变量的方法如下。

scala> var studentAge:Int =18 
studentAge: Int = 18

使用类型推断的形式定义变量的方法如下。

scala> var studentAge =18 
studentAge: Int = 18

那么var和val在什么地方不一样呢?现在使用var定义变量并修改值。

scala> var studentAge =18 
studentAge: Int = 18 
scala> studentAge = 19 
studentAge: Int = 19 

可以看到,使用var定义的学生年龄可以被修改。现在使用val定义变量并修改值。

scala> val studentAge =18 
studentAge: Int = 18 
scala> studentAge = 19 
<console>:12: error: reassignment to val
 studentAge=19
                 ^

在修改使用val定义的学生年龄时报错,说明使用val定义的变量是只读的,不可修改。

1.4.2 惰性赋值

使用var或者val定义的变量会直接加载到JVM内存,但在一些大型计算场景下,如执行一条复杂SQL语句,这个SQL语句可能有成百上千行,直接加载到JVM内存可能会造成很大的内存开销。针对这种情形,Scala提供了一个更好的赋值方式,也就是惰性赋值。当一个变量需要保存很大的数据,且不想直接加载到JVM内存,而是等需要执行时再加载时可以使用惰性赋值。其语法格式为:lazy val/var 变量名 = 表达式。

scala> lazy val sql = """ 
| select a.student_name ,b.class_info, c.teacher_name 
| from a ,b,c 
| where a.student_name in b.student_name 
| ... // 此为上百行查询条件
| group by a.class_score """ 
sql: String = <lazy>

1.4.3 字符串

Scala提供了多种字符串的定义方式,我们可以根据需要选择。具体定义方式和使用场景及其案例如下。

1.双引号

最简单的双引号,适用于大多数字符串定义,如定义一个学生的名称,值为Lisa。

scala> val studentName = "Lisa" 
studentName: String = Lisa 

2.插值表达式

插值表达式定义的语法格式为val/var 变量名 = s"${变量/表达式}字符串" ,适用于需要进行字符串拼接的场景,如定义一场考试的试卷信息,具体试卷类型由试卷编号和年级决定。

scala> val examNum = "001" 
examNum: String = 001 
scala> val classInfo = "classNo.1" 
classInfo: String = classNo.1 
scala> val examInfo = s"${classInfo}+${examNum}" 
examInfo: String = classNo.1+001 

3.三引号

三引号定义的语法格式为val 变量名 = """""",适用于有大段文字需要保存的场景,希望可以换行且不影响数据定义,方便阅读,如定义一个SQL语句。

scala> val sql = """ 
| select student_name, class_score, class_info 
| from score_table 
| where class_score > 90 """ 
sql: String = 
" select student_name, class_score, class_info 
from score_table 
where class_score > 90 " 

1.4.4 数据类型与运算符

Scala语言像其他编程语言一样有数据类型和运算符。

1.数据类型

Scala的数据类型和Java的大同小异。需要注意的是,Scala中所有数据类型的首字母都是大写的,整数类型是Int,而不是Integer(见表1-1)。

表1-1

数据类型

描述

Byte

8位有符号补码整数,数值区间为-128到127

Short

16位有符号补码整数,数值区间为-32768到32767

Int

32位有符号补码整数,数值区间为-2147483648到2147483647

Long

64位有符号补码整数,数值区间为-9223372036854775808到9223372036854775807

Float

32位IEEE 754标准的单精度浮点数

Double

64位IEEE 754标准的双精度浮点数

Char

16位无符号Unicode字符,区间值为U+0000到U+FFFF

String

字符序列

Boolean

值为true或false

2.运算符

Scala提供了丰富的内置运算符(见表1-2),主要作用是告诉编译器执行特定的数学或逻辑函数运算。

表1-2

类别

运算符

算术运算符

+、-、*、/、%

关系运算符

==、! =、>、<、>=、<=

逻辑运算符

&&、||、!

按位运算符

&、|、^

注意,Scala中没有++和--这两种运算符。要比较两个值是否相等,直接使用==或者!=即可,它们相当于Java中的equals。

scala> var a =0 
a: Int = 0 
scala> a++ 
<console>:13: error: value ++ is not a member of Int 
a++ 
^ 
scala> var b =0
a: Int = 0 
scala> b-- 
<console>:13: error: value -- is not a member of Int 
b-- 
^ 

3.Scala类型层次结构

Any是所有类型的父类,定义了一些通用的方法,如toString、equals、hashCode等。Any有两个子类:AnyVal是所有数值类型的父类,AnyRef是所有对象类型(引用类型)的父类。Nothing是所有类型的子类型,但是没有一个值是Nothing类型的,它的主要作用是给出非正常终止的信号,比如抛出异常、程序退出等。Null是所有引用类型的子类型,即AnyRef的子类,它由值null来表示,多用于和其他语言的互操作。

1.4.5 条件表达式

条件表达式就是if表达式,根据条件执行对应的命令。

1.有返回值的if表达式

Java中的if表达式是没有返回值的,如定义变量a =1、变量b=2,如果变量a等于变量b,则返回true,否则返回false。

scala> val a = 1  
a: Int = 1  
  
scala> val b = 2  
b: Int = 2  
  
scala> if (a == b) "true" else "false"  
res2: String = false  

可以看出,if表达式的返回值为false。在Java中,需要再定义一个变量去赋值,才能得到命令执行后的值。

Scala没有为三元运算设计特定的运算符,我们可以利用if表达式带有返回值的特性来模拟。

scala> val c = if (a == b) "true" else "false"  
c: String = false

2.块表达式

块表达式用于多行处理语句赋值一个变量,多行处理语句用{}包裹后作用于一个变量。

scala> val c = {  
     | print("c result is")  
     |  if (a == b) "true" else "false"  
     | }  
c result is c: String = false 

注意,上面示例中的if判断语句,其返回false,则c的值为false。那么,如果我们将print放在最后一行会有什么变化呢?

scala> val c  = {  
     | if (a == b) "true" else "false"  
     | print("c result is ")  
     | }  
c result is c: Unit = ()  

c的值变为null,这是因为print的返回值为null。

1.5 Scala控制结构和函数

Scala语言提供了基本的控制结构和丰富的函数。

1.5.1 for表达式

for 表达式的语法格式如下。

for( var x to Range ){  
   // 表达式
}
for( var x <- Range ){  
   // 表达式
} 

其中,to代表包含数组length。

1.简单循环

我们可以通过.to的方式使用for循环表达式输出数字1~10。

scala> val fun1 = 1.to(10)   // 使用函数.to生成数组
fun1: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)  
  
scala> for(i <-fun1) println(i)  
1  
2  
3  
4  
5  
6  
7  
8  
9  
10

我们也可以在for循环中直接使用to输出值。

scala> for(i <- 1 to 10) println(i)  
1  
2  
3  
4  
5  
6  
7  
8  
9  
10 

2.嵌套循环

乘法表是嵌套循环的简单示例。

scala> for(i<-1 to 9;j<-1 to i){  
     |     print(j+"*"+i+"="+j*i+" ")  
     |     if(j==i)  println()  
     | }  
1*1=1   
1*2=2 2*2=4   
1*3=3 2*3=6  3*3=9   
1*4=4 2*4=8  3*4=12 4*4=16   
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25   
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36   
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49   
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64   
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81

3.守卫

在for循环中添加if判断语句作为过滤条件,if判断语句称为守卫。

for(i <- 表达式/数组/集合 if 表达式) { 
// 表达式 
}  

比如,循环输出数字1~3,并且过滤掉数字2。

scala> for(i <- 1 to 3 if i != 2) {  
     |   print(i + " ")  
     | }  
1 3   

4.for推导式

for推导式主要用来将for循环后的数据返回到一个变量,它区别于Java代码用对象承接的方式,而是使用yield直接将数据返回。

val v = for(i <- Range ) yield 表达式

比如,循环打印数字1~3,并且过滤掉数字2,最后返回到一个变量。

scala> val res = for(i <- 1 to 3 if i != 2) yield {  
     |     i  
     | }  
res: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 3) 

1.5.2 while循环

对于while循环,Scala和Java是一样的,比如打印1~10的数字。

scala> var i = 1  
i: Int = 1  
  
scala>  while(i <= 10){  
     |     println(i)  
     |     i+=1  
     | }  
1  
2  
3  
4  
5  
6  
7  
8  
9  
10

1.5.3 函数

在Scala中,我们可以像操作数字一样将函数赋值给一个变量。

使用val 语句定义函数的方式为:val  函数名 = ([参数名: 参数类型]) => 函数体。

定义两个数相加的函数如下。

scala> val add = (a:Int, b:Int) => a + b  
add: (Int, Int) => Int = <function2>  
  
scala> add(1,2)  
res2: Int = 3  

1.5.4 方法和函数的区别

与函数不同,方法需要使用def来定义:def  方法名 (参数名:参数类型,参数名:参数类型):[返回类型] => {方法体}。

函数是对象(变量),而方法不是。方法在运行时,加载到JVM方法区;函数在运行时,加载到JVM堆内存。

函数是对象,继承FunctionN,函数对象有apply、curried、toString、tupled这些方法,方法则没有。方法无法像函数一样赋值给变量。

scala> val f = fun1  
<console>:12: error: missing argument list for method fun1  
Unapplied methods are only converted to functions when a function type is expected.  
You can make this conversion explicit by writing 'fun1 _' or 'fun1(_,_)' instead of 'fun1'.  
       val f = fun1  
               ^ 

想要将方法赋值给一个对象,可以先将方法转换为函数后再进行赋值。

将方法转换成函数的语法格式为val 函数名 = 方法名 _。

scala> val f = fun1 _  
f: (Int, Int) => Int = <function2>  
  
scala> def fun1(a:Int,b:Int)= a+b  
fun1: (a: Int, b: Int)Int  
  
scala> val f = fun1 _  
f: (Int, Int) => Int = <function2>

1.6 方法

在Scala中可以定义多个方法,Scala中的方法和Java类似,但是定义方式不同。

1.6.1 定义方法

定义方法的语法格式为:def  方法名(参数名:参数类型,参数名:参数类型):[返回类型] => {方法体}。

需要注意的是,参数列表中的参数类型不能省略,返回类型可省略,最后方法类型由Scala自动推断。当方法体中没有return明确返回值时,由块表达式的值决定返回值。比如,定义两个数相加的方法,并求出1+2的代码如下。

scala> def add(x:Int, y:Int):Int = x + y   
add: (x: Int, y: Int)Int  
  
scala> add(1,2)  
res5: Int = 3  

1.6.2 方法参数

方法参数主要有默认参数、带名参数和变长参数这3种。

1.默认参数

在方法定义参数的时候,给参数一个默认值,当调用该方法时,如果没有传入对应的参数值,那么会使用这个默认值进行处理。

比如,定义一个方法,让两个数相加,默认第一个参数值为3,第二个参数值为4,调用方法时不进行传值,求最后返回的值是多少。

scala> def add(x:Int=3, y:Int=4):Int = x + y   
add: (x: Int, y: Int)Int  
  
scala> add()  
res6: Int = 7  

2.带名参数

在调用方法时,指定参数名称进行传值。

比如,定义一个方法,让两个数相加,默认第一个参数值为3,第二个参数值为4,调用方法时指定第一个参数值为5,求最后返回的值是多少。

scala> def add(x:Int=3, y:Int=4):Int = x + y   
add: (x: Int, y: Int)Int  
  
scala> add(x=5)  
res7: Int = 9 

3.变长参数

在实际开发中,如果方法的参数个数不固定,那么可以使用变长参数来定义,在参数后面加“*”,表示参数可能是0个,也可能是多个,语法格式为:def  方法名(参数名:参数类型*):返回类型 = { 方法体 }。比如,定义一个多个数字相加的方法。

scala>  def add(n:Int*) = n.sum  
add: (n: Int*)Int  
  
scala> add(1,2,3,4,5,6,7,8,9,10)  
res8: Int = 55 

1.6.3 方法调用方式

在Scala中,有多种调用方法的方式:后缀调用法、中缀调用法、花括号调用法、无括号调用法。

1.后缀调用法

和在Java中调用方法一样,调用方式为:对象名.方法名(参数)。比如,计算3.14向上取整的代码如下。

scala> Math.ceil(3.14)  
res11: Double = 4.0

2.中缀调用法

Scala中的所有运算符都包含方法,例如加号,它底层就是一个方法。中缀调用法的调用方式为:对象名 方法名 参数(如果有多个参数,参数部分用圆括号包裹)。比如,计算3.14向上取整的代码如下。

scala> Math ceil 3.14  
res12: Double = 4.0 

3.花括号调用法

只有参数的个数为1时,才可以使用花括号({})调用,调用方式为:对象名.方法名 {参数}。比如,计算3.14向上取整的代码如下。

scala> Math.ceil{3.14}  
res13: Double = 4.0 

当传入多个参数时会报错。

scala> Math.ceil{3.14 , 3.14}  
<console>:1: error: ';' expected but ',' found.  
Math.ceil{3.14 , 3.14} 

4.无括号调用法

当定义的方法没有参数时,可以省略括号进行调用。

scala> def fun1()= {1+1}  
fun1: ()Int  
scala> fun1  
res1: Int = 2  

1.7 数组

和Java类似,Scala也提供了存储一组数据的容器,主要有定长数组和变长数组。Scala数组需要使用()取值,而且Scala数组中可以存放不同类型的数据,最终数据的类型是其共同父类。

比如,定义一个数组,里面包含Int、String和Boolean类型的值。

scala> val a = Array(1,"2",true)  
a: Array[Any] = Array(1, 2, true)

1.7.1 定长数组

定长数组,顾名思义,指的是数组长度是不可变的。定义定长数组的方法有如下两种。

方法1:val/var 变量名 = new Array[元素类型](数组长度)  // 通过指定长度定义  

比如,定义一个长度为10的Int类型数组。

scala> val a = new Array[Int](10)  
a: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)  

方法2:val/var 变量名 = Array(元素1, 元素2, 元素3,...)  // 使用元素直接定义  

比如,定义一个数组元素为1、2、3的数组。

scala> val a = Array(1,2,3)  
a: Array[Int] = Array(1, 2, 3)  

1.7.2 变长数组

变长数组是指数组元素的个数可以增减,即我们可以为其添加、删除、修改元素。最重要的一点是,要提前导入ArrayBuffer类:import scala.collection.mutable.ArrayBuffer

定义空变长数组的语法格式为:val/var a = ArrayBuffer[元素类型]()

定义带有初始元素的变长数组的语法格式为:val/var a = ArrayBuffer(元素1,元素2,元素3,...)。比如,定义一个变长数组,初始元素为1、2、3。

scala> import scala.collection.mutable.ArrayBuffer   
import scala.collection.mutable.ArrayBuffer  
  
scala> val a = ArrayBuffer(1, 2, 3)  
a: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3)  

添加元素到变长数组的方式为:+=。比如,向变长数组a中添加元素4。

scala> import scala.collection.mutable.ArrayBuffer   
import scala.collection.mutable.ArrayBuffer  
  
scala> val a = ArrayBuffer(1, 2, 3)  
a: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3)  
  
scala> a+=4  
res0: a.type = ArrayBuffer(1, 2, 3, 4)  

添加另一个数组到此变长数组的方式为:++=。比如,添加一个数组到变长数组。

scala> import scala.collection.mutable.ArrayBuffer   
import scala.collection.mutable.ArrayBuffer  
  
scala> val a = ArrayBuffer(1, 2, 3, 4)  
a: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4)  
  
scala> a++=Array(5,6)  
res2: a.type = ArrayBuffer(1, 2, 3, 4, 5, 6) 

删除变长数组中的一个元素的方式为:-=。比如,删除变长数组a中值等于6的元素。

scala> import scala.collection.mutable.ArrayBuffer   
import scala.collection.mutable.ArrayBuffer  
  
scala> val a = ArrayBuffer(1, 2, 3, 4, 5 ,6)  
a: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4, 5, 6)
scala> a-=6  
res3: a.type = ArrayBuffer(1, 2, 3, 4, 5)

1.7.3 遍历数组

和在Java中一样,在Scala中可以通过索引或者for表达式直接遍历数组。

scala> val a = Array(1,2,3)  
a: Array[Int] = Array(1, 2, 3)  
  
scala> for(i<-a) println(i)  

通过索引形式遍历数组的方式如下。

scala> val a = Array(1,2,3)  
a: Array[Int] = Array(1, 2, 3)  
  
scala> for(i <- 0 until a.length) println(a(i))  

1.8 元组和列表

1.8.1 元组

元组可以存放不同数据类型的数据,且元组中的元素是不可变的。比如,定义一个元组,其包含数字1、字符串"scala"和布尔值true,若试图改变数字1为数字3,会报错。

scala> val a =(1,"scala",true)  
a: (Int, String, Boolean) = (1,scala,true)  
  
scala> a._1  
res0: Int = 1  
  
scala> a._1 = 3  
<console>:12: error: reassignment to val  
       a._1 = 3 
               ^   

1.定义元组

定义元组有两种方式:一是使用()定义,二是使用->定义。使用->定义的形式只适用于两个元素。推荐使用()定义。

示例:定义一个元组,其包含数字1、字符串"scala"和布尔值true。

scala> val a =(1,"scala",true)  
a: (Int, String, Boolean) = (1,scala,true)  
scala> val a = 1->2  
a: (Int, Int) = (1,2)  
scala> val a = 1->2,3->4  
<console>:1: error: ';' expected but ',' found.  
val a = 1->2,3->4  
           ^  

2.访问元组

访问元组和访问数组不同,访问元组需要使用._index,index从1开始。使用._1访问的是元组中的第一个元素,使用._2访问的是元组中的第二个元素。

示例:定义一个元组,其包含数字1、字符串"scala"和布尔值true,并一次访问其中的元素。

scala> val a =(1,"scala",true)  
a: (Int, String, Boolean) = (1,scala,true)  
  
scala> a._1  
res1: Int = 1  
  
scala> a._2  
res2: String = scala  
  
scala> a._3  
res3: Boolean = true 

1.8.2 列表

列表是Scala中使用非常多的数据结构,其特点是可以保存重复的值,并且有先后顺序。列表和数组一样,也分为可变和不可变两种。使用可变列表前要先使用命令import scala.collection.mutable.ListBuffer。

1.不可变列表

不可变列表就是其元素和长度都不可更改。

创建不可变列表的语法格式如下。

val/var 变量名 = List(元素1, 元素2, 元素3,...)   //创建带有初始元素的不可变列表  
val/var 变量名 = 元素1 :: 元素2 :: Nil           //创建带有初始元素的不可变列表  
val/var 变量名 = Nil  // 创建一个空的不可变列表

根据上述3种创建方式,创建一个元素为0、1、2、3的列表。

scala> val list = List(0,1,2,3)  
list: List[Int] = List(0, 1, 2, 3)  
  
scala> val list = 0::1::2::3::Nil  
list: List[Int] = List(0, 1, 2, 3)  
  
scala> val list = Nil  
list: scala.collection.immutable.Nil.type = List()

2.可变列表

可变列表就是其元素和长度都可以改变。

创建可变列表的语法格式如下。

// 创建空的可变列表
val/var 变量名 = ListBuffer[Int]()    
// 创建带有初始元素的可变列表  
val/var 变量名 = ListBuffer(元素1,元素2,元素3,...)  

比如,创建一个空的可变列表。

scala> import scala.collection.mutable.ListBuffer  
import scala.collection.mutable.ListBuffer  
  
scala> val list = ListBuffer()  
list: scala.collection.mutable.ListBuffer[Nothing] = ListBuffer()  

比如,创建一个元素为0、1、2、3的可变列表。

scala> import scala.collection.mutable.ListBuffer  
import scala.collection.mutable.ListBuffer  
  
scala> val list = ListBuffer(0,1,2,3)  
list: scala.collection.mutable.ListBuffer[Int] = ListBuffer(0, 1, 2, 3)

对列表的常见操作是增删改查元素、将可变列表转变为不可变的列表和数组。增删改查操作方式和可变数组类似。

示例:定义一个可变列表,初始元素为0、1、2、3,获取第一个元素。

scala> import scala.collection.mutable.ListBuffer  
import scala.collection.mutable.ListBuffer  
  
scala> val list = ListBuffer(0,1,2,3)  
list: scala.collection.mutable.ListBuffer[Int] = ListBuffer(0, 1, 2, 3)  
  
scala> list(0)  
res0: Int = 0 

示例:定义一个可变列表,初始元素为0、1、2、3,添加一个元素4到尾部。

scala> import scala.collection.mutable.ListBuffer  
import scala.collection.mutable.ListBuffer  
  
scala> val list = ListBuffer(0,1,2,3)  
list: scala.collection.mutable.ListBuffer[Int] = ListBuffer(0, 1, 2, 3)  
  
scala> list +=4  
res1: list.type = ListBuffer(0, 1, 2, 3, 4)  

示例:定义一个可变列表,初始元素为0、1、2、3,删除元素3。

scala> import scala.collection.mutable.ListBuffer  
import scala.collection.mutable.ListBuffer  
  
scala> val list = ListBuffer(0,1,2,3)  
list: scala.collection.mutable.ListBuffer[Int] = ListBuffer(0, 1, 2, 3)  
  
scala> list -=3  
res2: list.type = ListBuffer(0, 1, 2) 

示例:定义一个可变列表,初始元素为0、1、2、3,追加一个新的列表。

scala> import scala.collection.mutable.ListBuffer  
import scala.collection.mutable.ListBuffer  
  
scala> val list = ListBuffer(0,1,2,3)  
list: scala.collection.mutable.ListBuffer[Int] = ListBuffer(0, 1, 2, 3)  
  
scala> list++=List(4,5)  
res3: list.type = ListBuffer(0, 1, 2, 3, 4, 5)

示例:定义一个可变列表,初始元素为0、1、2、3,将可变列表转变为不可变的列表。

scala> import scala.collection.mutable.ListBuffer  
import scala.collection.mutable.ListBuffer  
  
scala> val list = ListBuffer(0,1,2,3)  
list: scala.collection.mutable.ListBuffer[Int] = ListBuffer(0, 1, 2, 3)  
  
scala> list.toList  
res4: List[Int] = List(0, 1, 2, 3) 

示例:定义一个可变列表,初始元素为0、1、2、3,将可变列表转变为不可变的数组。

scala> import scala.collection.mutable.ListBuffer  
import scala.collection.mutable.ListBuffer  
  
scala> val list = ListBuffer(0,1,2,3)  
list: scala.collection.mutable.ListBuffer[Int] = ListBuffer(0, 1, 2, 3)  
  
scala> list.toArray  
res5: Array[Int] = Array(0, 1, 2, 3)  

3.列表常用操作

Scala也提供了针对列表的常用操作,比如,判断列表是否为空(isEmpty)。

scala> val list = List(0,1,2,3)  
list: List[Int] = List(0, 1, 2, 3)  
  
scala> list.isEmpty  
res0: Boolean = false 

示例:拼接两个列表(++),返回的是一个在内存中生成的新列表。

scala> val list = List(0,1,2,3)  
list: List[Int] = List(0, 1, 2, 3)  
  
scala> val tmpList = List(4,5)  
tmpList: List[Int] = List(4, 5)  
  
scala> list++tmpList  
res1: List[Int] = List(0, 1, 2, 3, 4, 5) 

示例:获取列表的首个元素(head)和剩余部分(tail)。head用于获取首个元素,tail用于获取除了第一个元素外的其余元素,返回的是一个在内存中生成的新列表。

scala> val list = List(0,1,2,3)  
list: List[Int] = List(0, 1, 2, 3)  
  
scala> list.head  
res0: Int = 0  
  
scala> list.tail  
res1: List[Int] = List(1, 2, 3)  

示例:反转(reverse)列表。

scala> val list = List(0,1,2,3)  
list: List[Int] = List(0, 1, 2, 3)  
  
scala> list.reverse  
res2: List[Int] = List(3, 2, 1, 0)  

示例:获取列表前缀(take)和后缀(drop)。

scala> val list = List(0,1,2,3)  
list: List[Int] = List(0, 1, 2, 3)  
  
scala> list.take(3)  
res6: List[Int] = List(0, 1, 2)  
  
scala> list.drop(1)  
res7: List[Int] = List(1, 2, 3)

示例:扁平化(flatten)。扁平化适用于一个列表中包含多个列表的场景。通过扁平化可以将某个列表中包含的列表元素提取出来放在另一个列表中,如果列表中有相同的元素,会保留。

scala> val list = List(List(0),List(2,3),List(1),List(0))  
list: List[List[Int]] = List(List(0), List(2, 3), List(1), List(0))  
  
scala> list.flatten  
res12: List[Int] = List(0, 2, 3, 1, 0)  

示例:拉链(zip)。将两个列表通过相同的索引组成一个元素为元组的列表。比如,对一个学生姓名列表和班级列表做拉链,可以求得每个同学分配到的班级。

scala> val studentName = List("student1","student2","student3")  
studentName: List[String] = List(student1, student2, student3)  
  
scala> val studentClass = List("class1","class2","class3")  
studentClass: List[String] = List(class1, class2, class3)  
  
scala> studentName.zip(studentClass)  
res13: List[(String, String)] = List((student1,class1), (student2,class2), (student3,class3))  

示例:拉开(unzip)。将一个拉链的列表拉开,生成包含两个列表的元组。

scala> val zipList = studentName.zip(studentClass)  
zipList: List[(String, String)] = List((student1,class1), (student2,class2), (student3,class3))  
scala> zipList.unzip  
res14: (List[String], List[String]) = (List(student1, student2, student3),List(class1, class2, class3))  

示例:转换字符串(toString)。将一个列表转换成字符串。

scala> val list = List(0,1,2,3)  
list: List[Int] = List(0, 1, 2, 3)  
  
scala> list.toString  
res0: String = List(0, 1, 2, 3)  

示例:生成字符串(mkString)。将列表通过分隔符转换成字符串。

scala> val list = List(0,1,2,3)  
list: List[Int] = List(0, 1, 2, 3)  
  
scala> list.mkString("\t")  
res2: String = 0   1   2   3 

示例:并集(union)。将两个列表取并集,不去重。如果需要去重,要调用distinct。

scala> val list1= List(0,1,2,3)  
list1: List[Int] = List(0, 1, 2, 3)  
  
scala> val list2 = List(2,4,5,6)  
list2: List[Int] = List(2, 4, 5, 6)  
  
scala> list1.union(list2)  
res3: List[Int] = List(0, 1, 2, 3, 2, 4, 5, 6)  
  
scala> list1.union(list2).distinct  // 去重
res4: List[Int] = List(0, 1, 2, 3, 4, 5, 6)  

示例:交集(intersect)。将两个列表取交集。

scala> val list1= List(0,1,2,3)  
list1: List[Int] = List(0, 1, 2, 3)  
  
scala> val list2 = List(2,4,5,6,3)  
list2: List[Int] = List(2, 4, 5, 6, 3)  
  
scala> list1.intersect(list2)  
res5: List[Int] = List(2, 3)  

示例:差集(diff)。list1.diff(list2) 表示获取list1中list2中不包含的元素。

scala> val list1= List(0,1,2,3)  
list1: List[Int] = List(0, 1, 2, 3)  
  
scala> val list2 = List(2,4,5,6,3)  
list2: List[Int] = List(2, 4, 5, 6, 3)  
  
scala> list1.diff(list2)  
res6: List[Int] = List(0, 1) 

1.8.3 Set集合

Scala和Java一样,也提供了没有重复元素的集合。Set 集合没有重复的元素,不保证插入的顺序。

同样,Set集合也分为可变和不可变两种。定义可变Set集合前,要手动导入Set所在包的路径:import scala.collection.mutable.Set。

1.不可变Set集合

创建不可变Set集合的语法格式如下。

// 创建一个空的不可变Set集合
val/var 变量名 = Set[类型]()    
// 给定元素来创建一个不可变Set集合 
val/var 变量名 = Set[类型](元素1, 元素2, 元素3,...)   

示例:创建一个不可变Set集合。

scala> val set = Set(1,2,3,1,2,3)  
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3)  

2.可变Set集合

示例:定义一个可变Set 集合,并添加新的元素到集合。

scala> val set = Set(1,2,3)  
set: scala.collection.mutable.Set[Int] = Set(1, 2, 3)  
  
scala> set += 4  
res14: set.type = Set(1, 2, 3, 4) 

示例:定义一个可变Set 集合,并移除其中的一个元素。

scala> import scala.collection.mutable.Set  
import scala.collection.mutable.Set  
  
scala> val set = Set(1,2,3)  
set: scala.collection.mutable.Set[Int] = Set(1, 2, 3)  
  
scala> set -= 3  
res16: set.type = Set(1, 2) 

3.Set集合常用操作

示例:获取Set集合大小(size)。

scala> val set = Set(1,2,3)  
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3)  
  
scala> set.size  
res7: Int = 3 

示例:遍历Set集合(与数组一致)。

scala> val set = Set(1,2,3)  
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3)  
  
scala> for(i <- set){  
     |     println(i)  
     | }  
1  
2  
3 

示例:添加一个元素,生成一个新的Set集合。

scala> val set = Set(1,2,3)  
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3)  
  
scala> set + 4  
res11: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 4)

示例:拼接一个Set集合,生成一个新的Set集合。

scala> val set = Set(1,2,3)  
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3)  
  
scala> set ++ Set(4,5)  
res12: scala.collection.immutable.Set[Int] = Set(5, 1, 2, 3, 4)  

示例:拼接一个列表,生成一个新的Set集合。

scala> val set = Set(1,2,3)  
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3)  
  
scala> set ++ List(4,5)  
res13: scala.collection.immutable.Set[Int] = Set(5, 1, 2, 3, 4)  

1.9 Map映射

和Java一样,键值对(key-value)的集合用Map表示,同样地,也分不可变Map和可变Map。在定义可变Map前也要先手动导入import scala.collection.mutable.Map。

1.9.1 不可变Map

定义不可变Map的语法格式如下。

val/var map = Map(键->值, 键->值, 键->值,...) // 推荐,可读性更好  
val/var map = Map((键, 值), (键, 值), (键, 值), (键, 值),...)

示例:定义一个学生成绩Map 集合,其包含学生姓名和学生成绩,获取其中一个学生的成绩。

scala> val map = Map("student1"->99 , "student2"->98)  
map: scala.collection.immutable.Map[String,Int] = Map(student1 -> 99, student2 -> 98)  
  
scala> map("student1")  
res17: Int = 99  

1.9.2 可变Map

定义之前手动导入import scala.collection.mutable.Map,定义语法格式与不可变Map的一致。

示例:定义一个学生成绩Map 集合,修改第一个学生的成绩为100。

scala> import scala.collection.mutable.Map  
import scala.collection.mutable.Map  
  
scala> val map = Map("student1"-> 99 ,"student2"->98)  
map: scala.collection.mutable.Map[String,Int] = Map(student2 -> 98, student1 -> 99)  
  
scala> map("student1") = 100  
  
scala> map  
res19: scala.collection.mutable.Map[String,Int] = Map(student2 -> 98, student1 -> 100)

1.9.3 Map基本操作

1.获取值(map(key))

此类方式适用于明确知道key(键)是什么的场景。当不确定key是什么的时候,为避免报错,使用getOrElse(key, defaultValue),当不存在key的时候使用默认值。

示例:定义一个学生成绩Map 集合,获取第一个学生的成绩。

scala> val map = Map("student1"-> 99 ,"student2"->98)  
map: scala.collection.immutable.Map[String,Int] = Map(student1 -> 99, student2 -> 98)  
scala> map("student1")  
res0: Int = 99  

示例:使用getOrElse获取值。

scala> val map = Map("student1"-> 99 ,"student2"->98)  
map: scala.collection.immutable.Map[String,Int] = Map(student1 -> 99, student2 -> 98)  
  
scala> map.getOrElse("student4","defaultValue")  
res6: Any = defaultValue 

示例:定义一个学生成绩Map 集合,获取所有学生姓名。

scala> val map = Map("student1"-> 99 ,"student2"->98)  
map: scala.collection.immutable.Map[String,Int] = Map(student1 -> 99, student2 -> 98)  
scala> map.keys  
res1: Iterable[String] = Set(student1, student2) 

示例:定义一个学生成绩Map 集合,获取所有学生成绩。

scala> val map = Map("student1"-> 99 ,"student2"->98)  
map: scala.collection.immutable.Map[String,Int] = Map(student1 -> 99, student2 -> 98)  
scala> map.values  
res0: Iterable[Int] = MapLike(99, 98)

示例:定义一个学生成绩Map 集合,打印所有学生和成绩。

scala> val map = Map("student1"-> 99 ,"student2"->98)  
map: scala.collection.immutable.Map[String,Int] = Map(student1 -> 99, student2 -> 98)  
scala> for((studentName,studentScore)<- map){  
     |     println("studentName is " + studentName + ",studentScore is " + studentScore)
     | }  
studentName is student1,studentScore is 99  
studentName is student2,studentScore is 98 

2.增加键值对

示例:

scala> val map = Map("student1"-> 99 ,"student2"->98)  
map: scala.collection.immutable.Map[String,Int] = Map(student1 -> 99, student2 -> 98)  
  
scala> map + ("student3"->97)  
res3: scala.collection.immutable.Map[String,Int] = Map(student1 -> 99, student2 -> 98, student3 -> 97) 

3.删除键值对

示例:

scala> val map = Map("student1"-> 99 ,"student2"->98)  
map: scala.collection.immutable.Map[String,Int] = Map(student1 -> 99, student2 -> 98)  
  
scala> map - "student2"  
res4: scala.collection.immutable.Map[String,Int] = Map(student1 -> 99) 

1.10 函数式编程

函数式编程是一种编程范式,其有自己的语言特性,如数据不可变、函数是第一公民等。

1.10.1 遍历(foreach)

对于数组,List、Set等函数使用的遍历方式涉及for表达式,使用foreach函数式编程进行遍历,可以使程序更简洁。

foreach(f: (A) ⇒ Unit): Unit 

参数:接收一个函数对象,函数的输入参数为集合的元素。

返回值:null。

foreach执行过程:集合中的每一个元素都经过函数f。

示例:定义一个列表,用foreach遍历。

scala> val list = List(1,2,3,4)  
list: List[Int] = List(1, 2, 3, 4)  
  
scala> list.foreach((x:Int)=>println(x))  

1.10.2 使用类型推断简化函数定义

上面的示例可以用更简单的方式实现,利用类型推断简化函数定义,Scala可以自动推断出集合中每个元素参数的类型,所以定义函数时,可以省略其参数列表的类型。

scala> val list = List(1,2,3,4)  
list: List[Int] = List(1, 2, 3, 4)  
  
scala> list.foreach(x=>println(x))  

1.10.3 使用下画线简化函数定义

当函数参数只在函数体中出现一次,而且函数体没有嵌套调用时,可以使用下画线来简化函数的定义。将上面使用自动类型推断的示例继续简化,使用下画线代替函数,可以使打印语句更简练。

scala> val list = List(1,2,3,4)  
list: List[Int] = List(1, 2, 3, 4)  
  
scala> list.foreach(println(_))  
1  
2  
3 

1.10.4 映射(map)

如果想要集合中的每一个元素都经过同一个函数进行处理,可以使用map。

def map[B](f: (A) ⇒ B): TraversableOnce[B]

泛型B指定最终返回的集合泛型;f:(A) ⇒ B传入一个函数对象,该函数接收一个类型A(要转换的列表元素),返回值为类型B ;TraversableOnce[B] 是B类型的集合。

示例:定义一个列表,并使其中每一个值都加1。

scala> val list = List(0,1,2,3)  
list: List[Int] = List(0, 1, 2, 3)  
  
scala> list.map(x=>x+1)  
res3: List[Int] = List(1, 2, 3, 4) 

同样,使用下画线简化函数定义。

scala> val list = List(0,1,2,3)  
list: List[Int] = List(0, 1, 2, 3)  
scala> list.map(_+1)  
res4: List[Int] = List(1, 2, 3, 4)  

1.10.5 扁平化映射(flatMap)

可以把flatMap理解为先进行map操作,再进行flatten操作,即将列表中的元素转换成List 进行处理后再“拍平”。

def flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): TraversableOnce[B] 

泛型B指定最终返回的集合泛型;f:(A) ⇒ B传入一个函数对象,该函数接收一个类型A(要转换的列表元素),返回值为类型B ;TraversableOnce[B] 是B类型的集合。

比如,一个列表中有多个文本行数据,每一元素均为一个班级的学生姓名,现在想要统计这个列表中姓名不一样的学生一共有多少,可使用map+flatten实现。

scala> val list = List("student1 student2 student3","student1 student4 student5")
list: List[String] = List(student1 student2 student3, student1 student4 student5)
  
scala> list.map(_.split(" ")).flatten.distinct  
res8: List[String] = List(student1, student2, student3, student4, student5)  
  
scala> list.map(_.split(" ")).flatten.distinct.length  
res9: Int = 5 

示例:使用flatMap实现。

scala> val list = List("student1 student2 student3","student1 student4 student5")
list: List[String] = List(student1 student2 student3, student1 student4 student5)
  
scala> list.flatMap(_.split(" "))  
res13: List[String] = List(student1, student2, student3, student1, student4, student5)  
  
scala> list.flatMap(_.split(" ")).distinct.length  
res14: Int = 5 

1.10.6 过滤(filter)

过滤符合一定条件的元素,并返回元素列表。

def filter(f: (A) ⇒ Boolean): TraversableOnce[A] 

f:(A) ⇒ Boolean传入一个函数对象,该函数接收一个类型A(要转换的列表元素),返回一个Boolean类型,满足条件为true,不满足为false ;TraversableOnce[A] 返回过滤条件等于true的元素,A为元素类型。

示例:定义一个列表,其包含某班级的所有学生成绩,过滤成绩大于80的元素。

scala> val list = List(80,50,98,99,33,67,93)  
list: List[Int] = List(80, 50, 98, 99, 33, 67, 93)  
  
scala> list.filter(_>80)  
res15: List[Int] = List(98, 99, 93)

1.10.7 排序

Scala中提供了3种排序方式:默认排序、指定字段排序、自定义排序。

1.默认排序(sorted)

示例:定义一个列表,其包含1、2、3、4、5元素,对其进行升序排列。

scala> val list = List(4,5,2,1,3)  
list: List[Int] = List(4, 5, 2, 1, 3)  
  
scala> list.sorted  
res16: List[Int] = List(1, 2, 3, 4, 5)  

2.指定字段排序(sortBy)

可以指定按特定字段排序,将传入的函数转换后再进行排序。

def sortBy[B](f: (A) ⇒ B): List[A] 

f:(A) ⇒ B传入一个函数对象,该函数接收一个类型A(要转换的列表元素),返回类型B的元素进行排序;List[A] 是返回的排序后的列表。

示例:定义一个列表,其包含"student1 90"、"student2 87"、"student3 99",并按学生成绩排序。

scala> val list =List("student1 90","student2 87","student3 99")  
list: List[String] = List(student1 90, student2 87, student3 99)  
  
scala> list.sortBy(_.split(" ")(1))  
res18: List[String] = List(student2 87, student1 90, student3 99)

3.自定义排序(sortWith)

自定义排序,是根据一个函数来进行自定义排序。

def sortWith(lt: (A, A) ⇒ Boolean): List[A] 

lt:(A,A) ⇒ Boolean传入一个比较大小的函数对象,该函数接收两个集合类型的元素参数,返回两个元素比较大小的结果,小于则返回true,大于则返回false。

示例:定义一个列表,其包含1、2、3、4、5元素,使用sortWith对其进行升序排列。

scala> val list = List(4,3,1,2,5)  
list: List[Int] = List(4, 3, 1, 2, 5)  
  
scala> list.sortWith((x,y) => if(x<y)true else false)  
res19: List[Int] = List(1, 2, 3, 4, 5)  

可以使用下画线来简化代码。

scala> val list = List(4,3,1,2,5)  
list: List[Int] = List(4, 3, 1, 2, 5)  
scala> list.sortWith(_ < _)  
res20: List[Int] = List(1, 2, 3, 4, 5)  // 简化  

1.10.8 分组(groupBy)

如果需要将数据分组后进行统计,那么使用groupBy。

def groupBy[K](f: (A) ⇒ K): Map[K, List[A]] 

[K]是分组字段的类型;f: (A) ⇒ K传入一个函数对象,该函数接收集合类型的元素参数,返回一个K类型的key,这个key会用来进行分组,相同的key放在一组;Map[K, List[A]] 返回一个映射,K为分组字段,List为这个分组字段对应的一组数据。

示例:定义一个列表,其包含"student1 90"、"student2 80"、"student3 80",并按成绩进行分组。

scala> val list = List("student1 90","student2 80","student3 80")  
list: List[String] = List(student1 90, student2 80, student3 80)  
  
scala> list.groupBy(_.split(" ")(1))  
res21: scala.collection.immutable.Map[String,List[String]] = Map(90 -> List
(student1 90), 80 -> List(student2 80, student3 80))

1.10.9 聚合(reduce)

聚合操作可以将一个列表中的数据合并为一个,其在统计分析中很常用。

def reduce[A1 >: A](op: (A1, A1) ⇒ A1): A1 

[A1 >: A] 中(下界)A1必须是集合类型的子类 ;op: (A1, A1) ⇒ A1传入一个函数对象,用来不断进行聚合操作,第一个A1类型参数为当前聚合后的变量,第二个A1类型参数为当前要进行聚合的元素;返回值A1是列表最终聚合成的一个元素。简单来说就是前一个元素和后一个元素聚合后,再与下一个元素聚合。

示例:定义一个列表,其包含1、2、3、4、5、6、7、8、9、10,求和。

scala> val list = List(1,2,3,4,5,6,7,8,9,10)  
list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)  
  
scala> list.reduce(_+_)  
res22: Int = 55  // 解析执行流程: 1+2 =3 , 3+3 = 6, ...

可以指定其计算方向,默认是从左到右。

scala> val list = List(1,2,3,4,5,6,7,8,9,10)  
list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)  
  
scala> list.reduce(_+_)  
res22: Int = 55  
  
scala> list.reduceLeft(_+_)  // 从左到右:1+2 =3 , 3+3 = 6, ...
res23: Int = 55  
  
scala> list.reduceRight(_+_)  // 从右到左:10+9 =19 , 19+8 = 27, ...
res24: Int = 55  

1.10.10 折叠(fold)

fold与reduce类似,但是多了一个初始值参数,在执行时会先指定初始值。

def fold[A1 >: A](z: A1)(op: (A1, A1) ⇒ A1): A1 

泛型[A1 >: A]中(下界)A1必须是集合类型的子类;z: A1是初始值;op: (A1, A1) ⇒ A1传入一个函数对象,用来不断进行折叠操作,第一个A1类型参数为当前折叠后的变量,第二个A1类型参数为当前要进行折叠的元素;返回值A1是列表最终折叠成的一个元素。

示例:定义一个列表,其包含1、2、3、4、5、6、7、8、9、10,求和。

scala> val list = List(1,2,3,4,5,6,7,8,9,10)  
list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)  
  
scala> list.fold(0)(_+_)   // 从左到右: 0+1=1 , 1+2 =3 , 3+3 = 6, ...
res25: Int = 55  

可以指定其计算方向,默认是从左到右。

scala> val list = List(1,2,3,4,5,6,7,8,9,10)  
list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)  
  
scala> list.fold(0)(_+_)  
res25: Int = 55  
  
scala> list.foldLeft(0)(_+_)  // 从左到右: 0+1=1 , 1+2 =3 , 3+3 = 6, ...
res26: Int = 55  
  
scala> list.foldRight(0)(_+_)  // 从右到左: 0+10 = 10 ,10+9 =19 , 19+8 = 27, ...
res27: Int = 55  

1.11 本章总结

本章重点讲解了Scala的语法基础,其中重点为数据类型和集合类型。Scala对比Java来说,不可变的特性贯穿了整个语法基础知识体系,包括定义变量时的val类型,默认定义集合为不可变型。想要定义可变集合,必须先手动引入import scala.collection.mutable。且作为一个面向函数编程的语言,Scala在大数据开发运用中,foreach、map、flatMap、reduce、groupBy、filter等操作随处可见,读者要深入了解并熟练掌握。

1.12 本章习题

1.完成Scala开发环境搭建,并掌握Scala语言的基本语法。

2.完成数组、元组、映射和函数式编程的代码实践。

相关图书

Spark分布式处理实战
Spark分布式处理实战
Apache Spark大数据分析:基于Azure Databricks云平台
Apache Spark大数据分析:基于Azure Databricks云平台
Spark和Python机器学习实战:预测分析核心方法(第2版)
Spark和Python机器学习实战:预测分析核心方法(第2版)
图解Spark 大数据快速分析实战
图解Spark 大数据快速分析实战
精通Spark数据科学
精通Spark数据科学
Spark机器学习实战
Spark机器学习实战

相关文章

相关课程