目录
10.7.5排序(sorted、sortBy、sortWith)
2.Scala第二章节
章节目标
- 掌握变量, 字符串的定义和使用
- 掌握数据类型的划分和数据类型转换的内容
- 掌握键盘录入功能
- 理解Scala中的常量, 标识符相关内容
2.1 输出语句和分号
2.1.1 输出语句
- 方式一: 换行输出
格式: println(里边写你要打印到控制台的数据);
- 方式二: 不换行输出
格式: print(里边写你要打印到控制台的数据);
注意: 不管是println(), 还是print()语句, 都可以同时打印多个值.格式为: println(值1, 值2, 值3...)
2.1.2 分号
Scala语句中, 单行代码最后的分号可写可不写。 如果是多行代码写在一行, 则中间的分号不能省略, 最后一条代码的分号可省略不写。
**示例**:
println("Hello, Scala!") //最后的分号可写可不写
//如果多行代码写在一行, 则前边语句的分号必须写, 最后一条语句的分号可以省略不写.
println("Hello"); println("Scala")
2.2 Scala中的常量
2.2.1 概述
常量指的是: 在程序的运行过程中, 其值不能发生改变的量。
2.2.2 分类
- 字面值常量(常用的有以下几种)
* 整型常量
* 浮点型常量
* 字符常量
* 字符串常量
* 布尔常量
* 空常量
2.自定义常量(稍后解释)
2.2.3 代码演示
//整型常量
println(10)
//浮点型常量
println(10.3)
//字符常量, 值要用单引号括起来
println('a')
//字符串常量, 值要用双引号括起来
println("abc")
//布尔常量, 值只有true和false
println(true)
//空常量
println(null)
2.3. Scala中的变量
2.3.1 概述
我们将来每一天编写scala程序都会定义变量, 那什么是变量, 它又是如何定义的呢?
变量, 指的就是在程序的执行过程中, 其值可以发生改变的量. 定义格式如下:
2.3.2 语法格式
Java变量定义
int a = 0;
在scala中,可以使用`val`或者`var`来定义变量,语法格式如下:
val/var 变量名:变量类型 = 初始值
其中
- `val`定义的是不可重新赋值的变量, 也就是自定义常量。
- `var`定义的是可重新赋值的变量。
注意: scala中定义变量时, 类型写在变量名后面
2.3.3 示例
需求:定义一个变量保存一个人的名字"Tom"
**步骤**
1. 打开scala解释器
2. 定义一个字符串类型的变量用来保存名字
**参考代码**
scala> val name:String = "tom"
name: String = tom
2.3.4 val和var变量的区别
**示例**
给名字变量进行重新赋值为Jim,观察其运行结果
**参考代码**
scala> name = "Jim"
<console>:12: error: reassignment to val
name = "Jim"
**示例**
使用`var`重新定义变量来保存名字"Tom",并尝试重新赋值为Jim,观察其运行结果
**参考代码**
scala> var name:String = "tom"
name: String = tom
scala> name = "Jim"
name: String = Jim
注意: 优先使用`val`定义变量,如果变量需要被重新赋值,才使用`var`
2.3.5 使用类型推断来定义变量
scala的语法要比Java简洁,我们可以使用一种更简洁的方式来定义变量。
**示例**
使用更简洁的语法定义一个变量保存一个人的名字"Tom"
**参考代码**
scala> val name = "tom"
name: String = tom
scala可以自动根据变量的值来自动推断变量的类型,这样编写代码更加简洁。
2.4 字符串
scala提供多种定义字符串的方式,将来我们可以根据需要来选择最方便的定义方式。
- 使用双引号
- 使用插值表达式
- 使用三引号
2.4.1 使用双引号
语法
val/var 变量名 = “字符串”
**示例**
有一个人的名字叫"hadoop",请打印他的名字以及名字的长度。
**参考代码**
scala> println(name + name.length)
hadoop6
2.4.2 使用插值表达式
scala中,可以使用插值表达式来定义字符串,有效避免大量字符串的拼接。
语法
val/var 变量名 = s"${变量/表达式}字符串"
注意:
- 在定义字符串之前添加`s`
- 在字符串中,可以使用`${}`来引用变量或者编写表达式
**示例**
请定义若干个变量,分别保存:"zhangsan"、23、"male",定义一个字符串,保存这些信息。
打印输出:name=zhangsan, age=23, sex=male
**参考代码**
scala> val name = "zhangsan"
name: String = zhangsan
scala> val age = 23
age: Int = 23
scala> val sex = "male"
sex: String = male
scala> val result = s"name=${name}, age=${age}, sex=${sex}"
result: String = name=zhangsan, age=23, sex=male
scala> println(result)
name=zhangsan, age=23, sex=male
2.4.3 使用三引号
如果有大段的文本需要保存,就可以使用三引号来定义字符串。例如:保存一大段的SQL语句。三个引号中间的所有内容都将作为字符串的值。
语法
val/var 变量名 = """字符串1
字符串2"""
**示例**
定义一个字符串,保存以下SQL语句
```sql
select
*
from
t_user
where
name = "zhangsan"
```
打印该SQL语句
**参考代码**
val sql = """select
| *
| from
| t_user
| where
| name = "zhangsan""""
println(sql)
2.4.4 扩展: 惰性赋值
在企业的大数据开发中,有时候会编写非常复杂的SQL语句,这些SQL语句可能有几百行甚至上千行。这些SQL语句,如果直接加载到JVM中,会有很大的内存开销, 如何解决这个问题呢?
当有一些变量保存的数据较大时,而这些数据又不需要马上加载到JVM内存中。就可以使用**惰性赋值**来提高效率。
语法格式:
lazy val/var 变量名 = 表达式
**示例**
在程序中需要执行一条以下复杂的SQL语句,我们希望只有用到这个SQL语句才加载它。
"""insert overwrite table adm.itcast_adm_personas
select
a.user_id,
a.user_name,
a.user_sex,
a.user_birthday,
a.user_age,
a.constellation,
a.province,
a.city,
a.city_level,
a.hex_mail,
a.op_mail,
a.hex_phone,
a.fore_phone,
a.figure_model,
a.stature_model,
b.first_order_time,
b.last_order_time,
...
d.month1_hour025_cnt,
d.month1_hour627_cnt,
d.month1_hour829_cnt,
d.month1_hour10212_cnt,
d.month1_hour13214_cnt,
d.month1_hour15217_cnt,
d.month1_hour18219_cnt,
d.month1_hour20221_cnt,
d.month1_hour22223_cnt
from gdm.itcast_gdm_user_basic a
left join gdm.itcast_gdm_user_consume_order b on a.user_id=b.user_id
left join gdm.itcast_gdm_user_buy_category c on a.user_id=c.user_id
left join gdm.itcast_gdm_user_visit d on a.user_id=d.user_id;"""
**参考代码**
scala> lazy val sql = """insert overwrite table adm.itcast_adm_personas
| select
| a.user_id,
....
| left join gdm.itcast_gdm_user_buy_category c on a.user_id=c.user_id
| left join gdm.itcast_gdm_user_visit d on a.user_id=d.user_id;"""
sql: String = <lazy>
2.5 标识符
2.5.1 概述
实际开发中, 我们会编写大量的代码, 这些代码中肯定会有变量, 方法, 类等。 那它们该如何命名呢? 这就需要用到标识符了。 标识符就是用来给变量, 方法, 类等起名字的。 Scala中的标识符和Java中的标识符非常相似。
2.5.2 命名规则
- 必须由`大小写英文字母, 数字, 下划线_, 美元符$`, 这四部分任意组合组成.
- 数字不能开头.
- 不能和Scala中的关键字重名.
- 最好做到见名知意。
2.5.3 命名规范
- 变量或方法: 从第二个单词开始, 每个单词的首字母都大写, 其他字母全部小写(小驼峰命名法).
zhangSanAge, student_Country, getSum
- 类或特质(Trait): 每个单词的首字母都大写, 其他所有字母全部小写(大驼峰命名法)
Person, StudentDemo, OrderItems
- 包: 全部小写, 一般是公司的域名反写, 多级包之间用.隔开
com.itheima.add, cn.itcast.update
2.6 数据类型
2.6.1 简述
数据类型是用来约束变量(常量)的取值范围的. Scala也是一门强类型语言, 它里边的数据类型绝大多数和Java一样。我们主要来学习
- 与Java不一样的一些用法
- scala中数据类型的继承体系
2.6.2 数据类型
基础类型 | 类型说明 |
Byte | 8位带符号整数 |
Short | 16位带符号整数 |
Int | 32位带符号整数 |
Long | 64位带符号整数 |
Char | 16位无符号Unicode字符 |
String | Char类型的序列(字符串) |
Float | 32位单精度浮点数 |
Double | 64位双精度浮点数 |
Boolean | true或false |
注意下 scala类型与Java的区别
- scala中所有的类型都使用**大写字母**开头
- 整形使用`Int`而不是Integer
- scala中定义变量可以不写类型,让scala编译器自动推断
- Scala中默认的整型是Int, 默认的浮点型是: Double
2.6.3 Scala类型层次结构
类型 | 说明 |
Any | 所有类型的父类,它有两个子类AnyRef与AnyVal |
AnyVal | 所有数值类型的父类 |
AnyRef | 所有对象类型(引用类型)的父类 |
Unit | 表示空,Unit是AnyVal的子类,它只有一个的实例 |
Null | Null是AnyRef的子类,也就是说它是所有引用类型的子类。它的实例是 |
Nothing | 所有类型的子类 |
2.6.4 思考题
以下代码是否有问题?
val b:Int = null
Scala会解释报错Null类型并不能转换为Int类型,说明Null类型并不是Int类型的子类
2.7类型转换
2.7.1 概述
当Scala程序在进行运算或者赋值动作时, 范围小的数据类型值会自动转换为范围大的数据类型值, 然后再进行计算。例如: 1 + 1.1的运算结果就是一个Double类型的2.1。 而有些时候, 我们会涉及到一些类似于"四舍五入"的动作, 要把一个小数转换成整数再来计算。这些内容就是Scala中的类型转换。
Scala中的类型转换分为`值类型的类型转换`和`引用类型的类型转换`, 这里我们先重点介绍:`值类型的类型转换`。
- 值类型的类型转换分为:
- 自动类型转换
- 强制类型转换
2.7.2 自动类型转换
- 解释
范围小的数据类型值会自动转换为范围大的数据类型值, 这个动作就叫: 自动类型转换.
`自动类型转换从小到大分别为:Byte, Short, Char -> Int -> Long -> Float -> Double `
- 示例代码
val a:Int = 3
val b:Double = 3 + 2.21 //因为是int类型和double类型的值进行计算, 所以最终结果为: Double类型
val c:Byte = a + 1 //这样写会报错, 因为最终计算结果是Int类型的数据, 将其赋值Byte类型肯定不行。
2.7.3 强制类型转换
- 解释
范围大的数据类型值通过一定的格式(强制转换函数)可以将其转换成范围小的数据类型值, 这个动作就叫: 强制类型转换.
注意: 使用强制类型转换的时候可能会造成精度缺失问题!
- 格式
val/var 变量名:数据类型 = 具体的值.toXxx //Xxx表示你要转换到的数据类型
- 参考代码
val a:Double = 5.21
val b:Int = a.toInt
2.7.4 值类型和String类型之间的相互转换
- 值类型的数据转换成String类型
格式一:
val/var 变量名:String = 值类型数据 + ""
格式二:
val/var 变量名:String = 值类型数据.toString
**示例**
将Int, Double, Boolean类型的数据转换成其对应的字符串形式.
**参考代码**:
val a1:Int = 10
val b1:Double = 2.1
val c1:Boolean = true
//方式一: 通过和空字符串拼接的形式实现
val a2:String = a1 + ""
val b2:String = b1 + ""
val c2:String = c1 + ""
//方式二: 通过toString函数实现
val a3:String = a1.toString
val b3:String = b1.toString
val c3:String = c1.toString
- String类型的数据转换成其对应的值类型
格式:
val/var 变量名:值类型 = 字符串值.toXxx //Xxx表示你要转换到的数据类型
注意:
- String类型的数据转成Char类型的数据, 方式有点特殊, 并不是调用toChar, 而是toCharArray
- 这点目前先了解即可, 后续我们详细解释
**需求:**
将字符串类型的整数, 浮点数, 布尔数据转成其对应的值类型数据.
**参考代码:**
val s1:String = "100"
val s2:String = "2.3"
val s3:String = "false"
//将字符串类型的数据转成其对应的: Int类型
val a:Int = s1.toInt
//将字符串类型的数据转成其对应的: Double类型
val b:Double = s2.toDouble
//将字符串类型的数据转成其对应的: Boolean类型
val c:Boolean = s3.toBoolean
2. 8 键盘录入
2.8.1 概述
前边我们涉及到的数据, 都是我们写"死"的, 固定的数据, 这样做用户体验并不是特别好. 那如果这些数据是由用户录入, 然后我们通过代码接收, 就非常好玩儿了. 这就是接下来我们要学习的Scala中的"键盘录入"功能.
2.8.2 使用步骤
- 导包
格式: import scala.io.StdIn
- 通过`StdIn.readXxx()`来接收用户键盘录入的数据
- 接收字符串数据: StdIn.readLine()
- 接收整数数据: StdIn.readInt()
2.8.3 示例
- 提示用户录入字符串, 并接收打印.
println("请录入一个字符串: ")
val str = StdIn.readLine()
println("您录入的字符串内容为: " + str)
- 提示用户录入整数, 并接收打印.
println("请录入一个整数: ")
val num = StdIn.readInt()
println("您录入的数字为: " + num)
2.9. 案例: 打招呼
2.9.1 概述
聊了这么久, 赶紧来和小伙伴儿们来打个招呼吧.
需求: 提示用户录入他/她的姓名和年龄, 接收并打印.
2.9.2 具体步骤
- 提示用户录入姓名.
- 接收用户录入的姓名.
- 提示用户录入年龄.
- 接收用户录入的年龄.
- 将用户录入的数据(姓名和年龄)打印到控制台上.
2.9.3 参考代码
//1. 提示用户录入姓名.
println("请录入您的姓名: ")
//2. 接收用户录入的姓名.
val name = StdIn.readLine()
//3. 提示用户录入年龄.
println("请录入您的年龄: ")
//4. 接收用户录入的年龄.
val age = StdIn.readInt()
//5. 将用户录入的数据(姓名和年龄)打印到控制台上.
println(s"大家好, 我叫${name}, 我今年${age}岁了, 很高兴和大家一起学习Scala!")
3.Scala第三章节
章节目标
- 理解运算符的相关概述
- 掌握算术, 赋值, 关系, 逻辑运算符的用法
- 掌握交换变量案例
- 理解位运算符的用法
3.1 算术运算符
3.1.1 运算符简介
用来拼接变量或者常量的符号就叫: 运算符, 而通过运算符连接起来的式子就叫: 表达式。 实际开发中, 我们会经常用到它.
例如:
10 + 3 这个就是一个表达式, 而+号, 就是一个运算符.
注意: 在Scala中, 运算符并不仅仅是运算符, 也是函数的一种, 这点大家先了解即可, 后续我们详细讲解.
3.1.2 运算符的分类
- 算术运算符
- 赋值运算符
- 关系运算符
- 逻辑运算符
- 位运算符
注意: Scala中是没有三元运算符的, 被if-else给替代了。
同一优先级的运算符,运算次序由结合方向所决定。
简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符
3.1.3 算术运算符
算术运算符指的是用来进行算术操作的符号, 常用的有以下几种:
运算符 | 功能解释 |
+ | 加号,功能有3点. 1)表示正数 2)普通的加法操作 3)字符串的拼接 |
- | 减号,功能有2点. 1)表示负数 2)普通的减法操作 |
* | 乘号,用于获取两个数据的乘积 |
/ | 除法,用于获取两个数据的商 |
% | 取余(也叫取模),用于获取两个数据的余数 |
注意:
1). Scala中是没有++, --这两个算术运算符的, 这点和Java中不同.
2). 整数相除的结果, 还是整数. 如果想获取到小数, 则必须有浮点型数据参与.
例如: 10 / 3 结果是3 10 / 3.0 结果是: 3.3333(无限循环)`
3). 关于+号拼接字符串: 任意类型的数据和字符串拼接, 结果都将是一个新的字符串.
4). 关于%操作, 假设求`a % b`的值, 它的底层原理其实是: `a - a/b * b`
3.1.4 代码演示
需求: 演示算术运算符的常见操作.
参考代码:
//演示+号操作
println(+3)
println(10 + 3)
println("hello" + 10)
//演示-号操作
println(-5)
println(10 - 5)
//演示*号操作
println(5 * 3)
//演示/号操作
println(10 / 3)
println(10 / 3.0)
//演示%(取余)操作
println(10 % 3) //结果是1, 具体运算过程: 10 - 10/3 * 3 = 10 - 3 * 3 = 1
println(10 % -3) //结果是1, 具体运算过程: 10 - 10/-3 * -3 = 10 - -3 * -3 = 10 - 9 = 1
println(-10 % 3) //结果是-1, 具体运算过程: -10 - -10/3 * 3 = -10 - -3 * 3 = -10 + 9 = -1
3.2. 赋值运算符
3.2.1 概述
赋值运算符指的是用来进行赋值操作的符号。 例如: 把一个常量值, 或者一个变量值甚至是某一段代码的执行结果赋值给变量, 这些都要用到赋值运算符。
3.2.2 分类
赋值运算符常用的有两类
- 基本赋值运算符
`=`就是基本的赋值运算符, 例如: var a:Int = 3, 就是把常量值3赋值给变量a
- 扩展赋值运算符
`+=, -=, *=, /=, %=`
注意:
1). 赋值运算符的左边必须是: 变量, 不能是常量。 例如: 3 = 5, 这种写法就是错误的.
2). 关于扩展赋值运算符, 其实就是把左边的数据和右边的数据进行指定的操作, 然后把结果赋值给左边.
例如; a += 3 就是把变量a的值和常量3进行加法操作, 然后把结果赋值给变量a
3.2.3 代码演示
//将常量值1赋值给变量a
var a:Int = 1 //注意: 因为后续代码要修改变量a的值, 所以变量a要用var修饰
//对变量a进行加3操作, 然后把结果重新赋值给变量a
a += 3 //a的最终结果为: a = 4
//对变量a进行减2操作, 然后把结果重新赋值给变量a
a -= 2 //a的最终结果为: a = 2
//对变量a进行乘3操作, 然后把结果重新赋值给变量a
a *= 3 //a的最终结果为: a = 6
//对变量a进行除2操作, 然后把结果重新赋值给变量a
a /= 2 //a的最终结果为: a = 3
//对变量a和2进行取余操作, 然后把结果重新赋值给变量a
a %= 2 //a的最终结果为: a = 1
3.3. 关系运算符
3.3.1 概述
关系运算符指的是用来进行比较操作的符号。例如: 数据是否相等, 是否不等, 数据1大还是数据2大...等这些操作.
运算符 | 功能解释 |
> | 用来判断前边的数据`是否大于`后边的数据 |
>= | 用来判断前边的数据`是否大于或者等于`后边的数据 |
< | 用来判断前边的数据`是否小于`后边的数据 |
<= | 用来判断前边的数据`是否小于或者等于`后边的数据 |
== | 用来判断两个数据`是否相等` |
!= | 用来判断两个数据`是否不等` |
3.3.2 分类
注意:
1). 关系表达式不管简单还是复杂, 最终结果一定是Boolean类型的值, 要么是true, 要么是false.
2). 千万不要把==写成=, 否则结果可能不是你想要的.
3.3.3 代码演示
//定义两个Int类型的变量a, b, 分别赋值为3, 5
var a:Int = 3
var b:Int = 5
//判断a是否大于b, 并打印结果
println(a > b) //false
//判断a是否大于等于b, 并打印结果
println(a >= 3) //true
//判断a是否小于b, 并打印结果
println(a < b) //true
//判断a是否小于等于b, 并打印结果
println(a <= 3) //true
//判断a和b是否不等, 并打印结果
println(a != b) //true
//判断a和b是否相等, 并打印结果
println(a == b) //false
//如果把==写成了=, 其实是把变量b的值赋值给变量a
println(a = b) //输出结果是一对小括号"()", 即: 没有打印值.
println(a) //再次打印变量a, 打印结果是:5
3.3.4 关系运算符延伸
学过Java的同学会发现, 上述的Scala中的关系运算符用法和Java中是一样的, 那有和Java不一样的地方吗?
答案是: 有.
| 需求描述 | Scala代码 | Java代码 |
| 比较数据值 | == 或者 != | equals()方法 |
| 比较引用值(地址值) | eq方法 | == 或者 != |
**示例**
有一个字符串"abc",再创建第二个字符串,值为:在第一个字符串后拼接一个空字符串。
然后使用比较这两个字符串是否相等、再查看它们的引用值是否相等。
**参考代码**
val s1 = "abc"
val s2 = s1 + ""
s1 == s2 //结果是: true, 因为比较的是 数据值
s1.eq(s2) //结果是: false, 因为比较的是 地址值
3.4 逻辑运算符
3.4.1 概述
逻辑运算符指的是`用来进行逻辑操作的符号`。 可以简单理解为它是: 组合判断. 例如: 判断多个条件是否都满足, 或者满足其中的某一个, 甚至还可以对某个判断结果进行取反操作。
3.4.2 分类
运算符 | 功能解释 |
&& | 逻辑与,要求所有条件都满足(即:结果为true), 简单记忆:有false则整体为false. |
|| | 逻辑或,要求只要满足任意一个条件即可,简单记忆:有true则整体为true. |
! | 逻辑非,用来进行取反操作的. |
注意:
1). 逻辑表达式不管简单还是复杂, 最终结果一定是Boolean类型的值, 要么是true, 要么是false.
2). 在Scala代码中, 不能对一个Boolean类型的数据进行**连续取反**操作, 但是在Java中是可以的.
* 即: !!false, 这样写会报错, 不支持这种写法.
3.4.3 代码演示
//相当于: false && true
println(3 > 5 && 2 < 3) //结果为: false
//我们可以简写代码为:
//逻辑与: 有false则整体为false.
println(false && true) //结果为: false
println(true && false) //结果为: false
println(false && false) //结果为: false
println(true && true) //结果为: true
println(false || true) //结果为: true
println(true || false) //结果为: true
println(false || false) //结果为: false
println(true || true) //结果为: true
println(!false) //结果为: true
println(!true) //结果为: false
println(!!true) //这样写会报错, Scala不支持这种写法, 但是Java代码支持这种写法.
3.5 位运算符
3.5.1 铺垫知识
要想学好`位运算符`, 你必须得知道三个知识点:
1. 什么是进制
2. 什么是8421码
3. 整数的原码, 反码, 补码计算规则
5.1.1 关于进制
通俗的讲, 逢几进一就是几进制, 例如: 逢二进一就是二进制, 逢十进一就是十进制, 常用的进制有以下几种:
| 进制名称 | 数据组成规则 | 示例 |
| -------- | ------------------------------------------------------------ | ---------------------- |
| 二进制 | 数据以0b(大小写均可)开头, 由数字0和1组成 | 0b10001001, 0b00101010 |
| 八进制 | 数据以0开头, 由数字0~7组成 | 064, 011 |
| 十进制 | 数据直接写即可, 无特殊开头, 由数字0~9组成 | 10, 20, 333 |
| 十六进制 | 数据以0x(大小写均可)开头, 由数字0~9, 字母A-F组成(大小写均可) | 0x123F, 0x66ABC |
注意:关于二进制的数据, 最前边的那一位叫: 符号位, 0表示正数, 1表示负数. 其他位叫: 数值位.
例如: 0b10001001 结果就是一个: 负数, 0b00101010 结果就是一个: 正数.
5.1.2 关于8421码
8421码就是用来描述`二进制位和十进制数据之间的关系的`, 它可以帮助我们快速的计算数据的二进制或十进制形式.
8421码对应关系如下:
**二进制位** 0 0 0 0 0 0 0 0
**对应的十进制数据** 128 64 32 16 8 4 2 1
> 注意:
>
> 1. 计算规则: 二进制位从右往左数, 每多一位, 对应的十进制数据 乘以2.
> 2. 二进制和十进制相互转换的小技巧:
>
> * 二进制转十进制: 获取该二进制位对应的十进制数据, 然后累加即可.
> * 例如: 0b101对应的十进制数据计算步骤: 4 + 0 + 1 = 5
> * 十进制转二进制: 对十进制数据进行拆解, 看哪些数字相加等于它, 然后标记成二进制即可.
> * 例如: 10 对应的二进制数据计算步骤: 10 = 8 + 2 = 0b1010
5.1.3 关于整数的原反补码计算规则
所谓的原反补码, 其实指的都是二进制数据, 把十进制的数据转成其对应的二进制数据, 该二进制数据即为: 原码.
> 注意: 计算机底层存储, 操作和运算数据, 都是采用`数据的二进制补码形式`来实现的.
* 正数
* 正数的原码, 反码, 补码都一样, 不需要特殊计算.
* 负数
* 负数的反码计算规则: 原码的符号位不变, 数值位按位取反(以前为0现在为1, 以前为1现在为0)
* 负数的补码计算规则: 反码 + 1
#### 5.2 概述
位运算符指的就是`按照位(Bit)来快速操作数据值`, 它只针对于整型数据. 因为计算机底层存储, 操作, 运算采用的都是数据的二进制补码形式, 且以后我们要经常和海量的数据打交道, 为了提高计算效率, 我们就可以使用位运算符来实现快速修改数据值的操作.
#### 5.3 分类
| 运算符 | 功能解释 |
| ------ | ------------------------------------------------------------ |
| & | 按位与, 规则: 有0则0, 都为1则为1. |
| \| | 按位或, 规则: 有1则1, 都为0则为0. |
| ^ | 按位异或, 规则: 相同为0, 不同为1. |
| ~ | 按位取反, 规则: 0变1, 1变0. |
| << | 按位左移, 规则: 每左移一位, 相当于该数据乘2, 例如: 2 << 1, 结果为4 |
| \>> | 按位右移, 规则: 每右移一位, 相当于该数据除2, 例如: 6 >> 1, 结果为3 |
> 注意:
>
> 1. 位运算符只针对于整型数据.
> 2. 运算符操作的是数据的二进制补码形式.
> 3. 小技巧: 一个数字被同一个数字位异或两次, 该数字值不变. 即: 10 ^ 20 ^ 20, 结果还是10
#### 5.4 代码演示
```scala
//定义两个变量a和b, 初始化值分别为: 3, 5
val a = 3 //二进制数据: 0000 0011
val b = 5 //二进制数据: 0000 0101
//结果为: 0000 0001, 转化成十进制, 结果为: 1
println(a & b) //打印结果为: 1
//结果为: 0000 0111, 转化成十进制, 结果为: 7
println(a | b) //打印结果为: 7
//结果为: 0000 0110, 转换成十进制, 结果为: 6
println(a ^ b) //打印结果为: 6
//计算流程: 1111 1100(补码) -> 1111 1011(反码) -> 1000 0100(原码) -> 十进制数据: -4
println(~ a) //打印结果为: -4
//计算流程: 1000 0011(-3原码) -> 1111 1100(-3反码) -> 1111 1101(-3补码) -> 0000 0010(取反后新补码) -> 十进制数据: 2
println(~ -3) //打印结果为: 2
//计算流程: 0000 0011(3的补码) -> 0000 1100(新的补码) -> 十进制数据: 12
println(a << 2) //打印结果为: 12
//计算流程: 0000 0011(3的补码) -> 0000 0001(新的补码) -> 十进制数据: 1
println(a >> 1) //打印结果为: 1
println(a ^ b ^ b) //打印结果为: 3
3.6 案例: 交换两个变量的值
3.6.1 需求
已知有两个Int类型的变量a和b, 初始化值分别为10和20, 请写代码实现变量a和变量b的值的交换.
即最终结果为: a=20, b=10.
注意: 不允许直接写`a=20, b=10`这种代码.
3.6.2 参考代码
* **方式一: 通过算术运算符实现.**
//定义两个Int类型的变量a和b, 初始化值分别为10和20
var a = 10
var b = 20
//将变量a和b的计算结果赋值给变量a
a = a + b //a = 30, b = 20
//计算并赋值
b = a - b //a = 30, b = 10
a = a - b //a = 20, b = 10
//打印结果
println("a: " + a) //a: 20
println("b: " + b) //b: 10
* **方式二: 通过定义临时变量实现**
//定义两个Int类型的变量a和b, 初始化值分别为10和20
var a = 10
var b = 20
//定义临时变量temp, 记录变量a的值
var temp = a //a = 10, b = 20, temp = 10
//把变量b的值赋值给a
a = b //a = 20, b = 20, temp = 10
//把临时变量temp的值赋值给b
b = temp //a = 20, b = 10, temp = 10
//打印结果
println("a: " + a) //a: 20
println("b: " + b) //b: 10
* **方式三: 通过位运算符实现**
//定义两个Int类型的变量a和b, 初始化值分别为10和20
var a = 10
var b = 20
//定义临时变量temp, 记录变量a和b的位异或值(这个值不需要我们计算)
var temp = a ^ b //即: temp = 10 ^ 20
//通过位异或进行交换变量值
a = a ^ temp //运算流程: a = a ^ temp = a ^ a ^ b = 10 ^ 10 ^ 20 = 20
b = b ^ temp //运算流程: b = b ^ temp = b ^ a ^ b = 20 ^ 10 ^ 20 = 10
//打印结果
println("a: " + a) //a: 20
println("b: " + b) //b: 10
4.Scala第四章节
章节目标
- 掌握分支结构的格式和用法
- 掌握for循环和while循环的格式和用法
- 掌握控制跳转语句的用法
- 掌握循环案例
- 理解do…while循环的格式和用法
4.1 流程控制结构
4.1.1 概述
在实际开发中, 我们要编写成千上万行代码, 代码的顺序不同, 执行结果肯定也会受到一些影响, 并且有些代码是满足特定条件才能执行的, 有些代码是要重复执行的. 那如何合理规划这些代码呢? 这就需要用到: 流程控制结构了.
4.1.2 分类
- 顺序结构
- 选择(分支)结构
- 循环结构
注意: Scala和Java中的流程控制结构是基本一致的.
4.2. 顺序结构
4.2.1 概述
顺序结构是指: 程序是按照从上至下, 从左至右的顺序, 依次逐行执行的, 中间没有任何判断和跳转。
注意: 顺序结构是Scala代码的默认流程控制结构.
4.2.2 代码演示
val a = 10
println("a: " + a) //打印结果为10
println("键盘敲烂, ")
println("月薪过万! ")
4.2.3 思考题
下边这行代码的打印结果应该是什么呢?
println(10 + 10 + "Hello,Scala" + 10 + 10)
提示: 代码是按照从上至下, 从左至右的顺序, 依次逐行执行的.
4.3选择结构(if语句)
4.3.1 概述
选择结构是指: 某些代码的执行需要依赖于特定的判断条件, 如果判断条件成立, 则代码执行, 否则, 代码不执行.
4.3.2 分类
- 单分支
- 双分支
- 多分支
4.3.3 单分支
所谓的单分支是指: 只有一个判断条件的if语句.
- 格式
if(关系表达式) {
//具体的代码
}
注意: 关系表达式不管简单还是复杂, 结果必须是Boolean类型的值.
2. 执行流程
a)先执行关系表达式, 看其结果是true还是false.
b)如果是true, 则执行具体的代码, 否则, 不执行.
c)如图:
d)示例
**需求: **
定义一个变量记录某个学生的成绩, 如果成绩大于或者等于60分, 则打印: 分数及格.
**参考代码**
//定义变量, 记录成绩
val score = 61
//判断成绩是否不小于60分
if(score >= 60) {
println("成绩及格")
}
4.3.4 双分支
所谓的双分支是指: 只有两个判断条件的if语句.
- 格式
if(关系表达式) {
//代码1
} else {
//代码2
}
- 执行流程
-
- 先执行关系表达式, 看其结果是true还是false.
- 如果是true, 则执行代码1. 如果是false, 则执行代码2.
- 如图:
- 示例
**需求: **
定义一个变量记录某个学生的成绩, 如果成绩大于或者等于60分, 则打印: 分数及格, 否则打印分数不及格.
**参考代码**
//定义变量, 记录成绩
val score = 61
//判断成绩是否不小于60分
if(score >= 60) {
println("成绩及格")
} else {
println("成绩不及格")
}
4.3.5 多分支
所谓的多分支是指: 有多个判断条件的if语句.
- 格式
if(关系表达式1) {
//代码1
} else if(关系表达式2) {
//代码2
}else if(关系表达式n) { //else if可以有多组
//代码n
} else {
//代码n+1 //所有的关系表达式都不成立的时候, 执行这里的代码.
}
- 执行流程
-
- 先执行关系表达式1, 看其结果是true还是false.
- 如果是true, 则执行代码1, 分支语句结束. 如果是false, 则执行关系表达式2, 看其结果是true还是false.
- 如果是true, 则执行代码2. 分支语句结束. 如果是false, 则执行关系表达式3, 看其结果是true还是false.
- 以此类推, 直到所有的关系表达式都不满足, 执行最后一个else中的代码.
- 如图:
- 示例
**需求: **
定义一个变量记录某个学生的成绩, 根据成绩发放对应的奖励, 奖励机制如下:
[90, 100] -> VR设备一套
[80, 90) -> 考试卷一套
[0, 80) -> 组合拳一套
其他 -> 成绩无效
**参考代码**
//定义变量, 记录成绩
val score = 80
//根据成绩发放对应的奖励
if(score >= 90 && score <= 100) {
println("VR设备一套")
} else if(score >= 80 && score < 90) {
println("考试卷一套")
} else if(score >= 0 && score < 80) {
println("组合拳一套")
} else {
println("成绩无效")
}
4.3.6 注意事项
if语句在使用时, 要注意的事项有以下三点:
- 和Java一样, 在Scala中, 如果大括号{}内的逻辑代码只有一行, 则大括号可以省略.
- 在scala中,条件表达式也是有返回值的
- 在scala中,没有三元表达式,可以使用if表达式替代三元表达式
**示例**
定义一个变量sex,再定义一个result变量,如果sex等于"male",result等于1,否则result等于0
**参考代码**
//定义变量, 表示性别
val sex = "male"
//定义变量, 记录if语句的返回值结果
val result = if(sex == "male") 1 else 0
//打印结果为 result: 1
println("result: " + result)
4.3.7 嵌套分支
有些时候, 我们会涉及到"组合判断", 即一个分支结构中又嵌套了另一个分支结构, 这种写法就叫嵌套分支. 里边的那个分支结构叫: 内层分支, 外边的那个分支结构叫: 外层分支.
**示例**
定义三个变量a,b,c, 初始化值分别为: 10, 20, 30, 通过if分支语句, 获取其中的最大值.
**思路分析**
- 定义三个变量a, b, c, 分别记录要进行操作的值.
- 定义变量max, 用来记录获取到的最大值.
- 先判断a是否大于或者等于b.
- 条件成立, 说明 a大(或者等于b), 接着比较a和c的值, 获取最大值, 并将结果赋值给变量max
- 条件不成立, 说明 b大, 接着比较b和c的值, 获取最大值, 并将结果赋值给变量max
- 此时, max记录的就是a, b, c这三个变量的最大值, 打印即可。
**参考代码**
//1. 定义三个变量a, b, c, 分别记录要进行操作的值.
val a = 10
val b = 20
val c = 30
//2. 定义变量max, 用来记录获取到的最大值.
var max = 0
//3. 先判断a是否大于或者等于b.
if(a >= b) {
//4. 走这里说明a大(或者等于b), 接着比较a和c的值
max = if(a >= c) a else c
} else {
//5. 走这里说明b大, 接着比较b和c的值
max = if(b >= c) b else c
}
//6. 打印max的值
println("max: " + max)
注意: 嵌套一般不超过3层.
4.3.8 扩展: 块表达式
-
-
- scala中,使用{}表示一个块表达式
- 和if表达式一样,块表达式也是有值的
- 值就是最后一个表达式的值
-
**问题**
请问以下代码,变量a的值是什么?
val a = {
println("1 + 1")
1 + 1
}
println("a: " + a)
4.4. 循环结构
4.4.1 概述
循环,指的是事物周而复始的变化。而Scala中的循环结构,是指: 使一部分代码按照次数或一定的条件反复执行的一种代码结构。例如: 打印10次"Hello, Scala!", 如果纯写输出语句, 需要写10次, 而通过循环来实现的话, 输出语句只需要写1次, 这样就变得很简单了.
4.4.2 分类
- for循环
- while循环
- do.while循环
注意: 这三种循环推荐使用for循环, 因为它的语法更简洁, 更优雅.
4.4.3 for循环
在Scala中, for的格式和用法和Java中有些差异, Scala中的for表达式功能更加强大.
- 格式
for(i <- 表达式/数组/集合) {
//逻辑代码
}
注意: 执行流程和Java一致
- 简单循环
**需求: **
打印10次"Hello, Scala!"
**参考代码: **
//定义一个变量, 记录1到10的数字
val nums = 1 to 10 //to是Scala中的一个关键字
//通过for循环, 打印指定的内容
for(i <- nums) {
println("Hello, Scala! " + i)
}
**上述代码可以简写成: **
for(i <- 1 to 10) println("Hello, Scala! " + i)
- 嵌套循环
**需求: **使用for表达式,打印以下字符, 每次只能输出一个"*"
```scala
*****
*****
*****
**步骤**
- 使用for表达式打印3行,5列星星
- 每打印5个星星,换行
**参考代码**
//写法一: 普通写法
for (i <- 1 to 3) { //外循环控制行数
for (j <- 1 to 5) { //内循环控制列数
print("*") //每次打印一个*
}
println() //打印完一行(5个*)之后, 记得换行
}
//写法二: 压缩版
for (i <- 1 to 3) {
//这是两行代码
for (j <- 1 to 5)
if (j == 5)
println("*")
else
print("*")
}
//写法三: 合并版
for (i <- 1 to 3; j <- 1 to 5)
if (j == 5)
println("*")
else
- 守卫
for表达式中,可以添加if判断语句,这个if判断就称之为守卫。我们可以使用守卫让for表达式更简洁。
**语法**
for(i <- 表达式/数组/集合 if 表达式) {
//逻辑代码
}
**示例**
使用for表达式打印1-10之间能够整除3的数字
**参考代码**
// 添加守卫,打印能够整除3的数字
for(i <- 1 to 10 if i % 3 == 0) println(i)
- for推导式
Scala中的for循环也是有返回值的, 在for循环体中,可以使用yield表达式构建出一个集合(可以简单理解为: 就是一组数据),我们把使用yield的for表达式称之为**推导式.**
**示例**
生成一个10、20、30...100的集合
**参考代码**
// for推导式:for表达式中以yield开始,该for表达式会构建出一个集合
val v = for(i <- 1 to 10) yield i * 10
println(v)
4.4.3 while循环
scala中while循环和Java中是一致的, 所以学起来非常简单.
- 格式
初始化条件
while(判断条件) {
//循环体
//控制条件
}
- 执行流程
-
- 执行初始化条件
- 执行判断条件, 看其结果是true还是false
- 如果是false则循环结束
- 如果是true则执行循环体
- 执行控制条件
- 返回第二步, 重复执行
- 示例
**需求: **
打印1-10的数字
**参考代码**
//初始化条件
var i = 1
//判断条件
while(i <= 10) {
//循环体
println(i)
//控制条件
i = i + 1
}
4.4.5 do…while循环
scala中do…while循环和Java中是一致的, 所以学起来非常简单.
- 格式
初始化条件
do{
//循环体
//控制条件
}while(判断条件)
- 执行流程
-
- 执行初始化条件
- 执行循环体
- 执行控制条件
- 执行判断条件, 看其结果是true还是false
- 如果是false则循环结束
- 如果是true则返回第2步继续执行
注意:
- do.while循环不管判断条件是否成立, 循环体都会执行一
- for循环, while循环都是如果判断条件不成立, 则循环体不执行
- 示例
**需求: **
打印1-10的数字
**参考代码**
//初始化条件
var i = 1
do{
//循环体
println(i)
//控制条件
i = i + 1
}while(i <= 10) //判断条件
4.4.6 break和continue
在scala中,类似Java和C++的break/continue关键字被移除了
如果一定要使用break/continue,就需要使用scala.util.control包下的Breaks类的**breakble**和**break**方法。
- 实现break
**用法**
1). 导包.
import scala.util.control.Breaks._
2). 使用breakable将for表达式包起来
3). for表达式中需要退出循环的地方,添加`break()`方法调用
**示例**
使用for表达式打印1-10的数字,如果遇到数字5,则退出for表达式
**参考代码**
// 导入scala.util.control包下的Break
import scala.util.control.Breaks._
breakable{
for(i <- 1 to 10) {
if(i == 5) break() else println(i)
}
}
- 实现continue
**用法**
continue的实现与break类似,但有一点不同:
注意:
1). 实现break是用breakable{}将整个for表达式包起来.
2). 而实现continue是用breakable{}将for表达式的循环体包含起来就可以了.
**示例**
用for表达式打印1~10之间, 所有不能整除3的数字.
// 导入scala.util.control包下的Break
import scala.util.control.Breaks._
for(i <- 1 to 100 ) {
breakable{
if(i % 3 == 0) break()
else println(i)
}
}
4.5 综合案例
4.5.1 九九乘法表
**需求: **
打印九九乘法表, 如下图:
**步骤**
1). 通过外循环控制打印的行数.
2). 通过内循环控制每行打印的列数.
注意: 因为列数是随着行数递增的, 即:
行数 | 该行的总列数 |
1 | 1 |
2 | 2 |
3 | 3 |
n | n |
结论: 如果用**i**表示行数, 那么该行的列数取值范围为: [1, i]
**参考代码**
* **方式一: 普通写法**
//外循环控制行
for(i <- 1 to 9) {
//内循环控制列
for(j <- 1 to i) {
print(s"${i} * ${j} = ${i * j}\t")
}
println() //别忘了换行
}
* **方式二: 合并版写法**
//外循环控制行
for(i <- 1 to 9; j <- 1 to i) {
print(s"${i} * ${j} = ${i * j}\t")
if(j == i) println() //别忘了换行
}
4.5.2 模拟登陆
**需求: **
老王要登陆黑马官网学习Scala, 假设老王的账号和密码分别为"itcast", "heima", 且同一账号只有3次登陆机会, 如果3次都录入错误, 则提示账号被锁定. 请用所学模拟该场景。
**步骤**
1). 导包
scala.io.StdIn
scala.util.control.Breaks._
2). 定义变量, 记录用户录入的账号和密码。
3). 因为涉及到break的动作, 所以要用breakable{}把整个for表达式包裹起来
4). 因为只有3次登陆机会, 所以推荐使用for循环。
5). 提示用户录入他/她的账号和密码, 并接收。
6). 判断用户录入的账号和密码是否正确。
7). 如果录入正确, 则提示"登陆成功, 开始学习Scala!", 循环结束.
8). 如果录入错误, 则判断是否还有登陆机会
有, 则提示"用户名或者密码错误, 您还有*次机会", 然后返回第5步继续执行。
没有, 则提示"账号被锁定, 请与管理员联系", 循环结束。
**参考代码**
//1). 导包
import scala.io.StdIn
import scala.util.control.Breaks._
//2). 定义变量, 记录用户录入的账号和密码
var username = ""
var password = ""
//3). 因为涉及到break的动作, 所以要用breakable{}把整个for表达式包裹起来
breakable {
//4). 因为只有3次登陆机会, 所以推荐使用for循环.
for(i <- 1 to 3) {
//5). 提示用户录入他/她的账号和密码, 并接收.
println("请录入您的账号: ")
username = StdIn.readLine()
println("请录入您的密码: ")
password = StdIn.readLine()
//6). 判断用户录入的账号和密码是否正确.
if(username == "itcast" && password == "heima") {
//7. 走到这里, 说明登陆成功, 循环结束.
println("登陆成功, 开始学习Scala吧!")
break()
} else {
//8. 走到这里, 说明登陆失败. 则判断是否还有登陆机会
if(i == 3) println("账号被锁定, 请与管理员联系!")
else println(s"用户名或者密码错误, 您还有${3 - i}次机会")
}
}
}
10. Scala第十章节
章节目标
1. 掌握数组, 元组相关知识点
2. 掌握列表, 集, 映射相关知识点
3. 了解迭代器的用法
4. 掌握函数式编程相关知识点
5. 掌握学生成绩单案例
10.1 数组
10.1.1 概述
数组就是用来存储多个同类型元素的容器, 每个元素都有编号(也叫: 下标, 脚标, 索引), 且编号都是从 0 开始数的。
Scala中, 有两种数组,一种是定长数组,另一种是变长数组。
10.1.2 定长数组
(1)特点
1). 数组的长度不允许改变.
2). 数组的内容是可变的.
(2)语法
- 格式一:通过指定长度定义数组
val/var 变量名 = new Array[元素类型](数组长度)
- 格式二:通过指定元素定义数组
val/var 变量名 = Array(元素 1 , 元素 2 , 元素 3 ...)
注意:
1). 在scala中,数组的泛型使用[]来指定.
2). 使用数组名(索引)来获取数组中的元素.
3). 数组元素是有默认值的, Int:0, Double:0.0, String: null
4). 通过数组名.length或者数组名.size来获取数组的长度.
(3)示例
需求
1). 定义一个长度为 10 的整型数组, 设置第 1 个元素为11, 并打印第 1 个元素.
2). 定义一个包含"java", "scala", "python"这三个元素的数组, 并打印数组长度.
参考代码
//案例: 演示定长数组
object ClassDemo01 {
def main(args: Array[String]): Unit = {
// 1. 定义一个长度为 10 的整型数组, 设置第 1 个元素为 11 , 并打印第 1 个元素.
val arr1 = new Array[Int](10)
arr1(0) = 11
println(arr1(0)) //打印数组的第 1 个元素.
println("-" * 15) //分割线
// 2. 定义一个包含"java", "scala", "python"这三个元素的数组, 并打印数组长度.
val arr2 = Array("java", "scala", "python")
println(arr2.length) //打印数组的长度
}
}
10.1.3 变长数组
(1)特点
数组的**长度和内容都是可变的,可以往数组中添加、删除元素.
(2)语法
创建变长数组,需要先导入ArrayBuffer类.
import scala.collection.mutable.ArrayBuffer
- 定义格式一: 创建空的ArrayBuffer变长数组
val/var 变量名 = ArrayBuffer[元素类型]()
- 定义格式二: 创建带有初始元素的ArrayBuffer变长数组
val/var 变量名 = ArrayBuffer(元素 1 ,元素 2 ,元素 3 ....)
(3)示例一: 定义变长数组
1. 定义一个长度为 0 的整型变长数组.
2. 定义一个包含"hadoop", "storm", "spark"这三个元素的变长数组.
3. 打印结果.
参考代码
// 1. 导包.
import scala.collection.mutable.ArrayBuffer
//案例: 演示变长数组
object ClassDemo02 {
def main(args: Array[String]): Unit = {
// 2. 定义一个长度为 0 的整型变长数组.
val arr1 = ArrayBuffer[Int]()
println(s"arr 1 : $arr1")
// 3. 定义一个包含"hadoop", "storm", "spark"这三个元素的变长数组.
val arr2 = ArrayBuffer("hadoop", "storm", "spark")
println(s"arr 2 : $arr2")
}
}
(4)示例二: 增删改元素
针对Scala中的变长数组, 可通过下述方式来修改数组中的内容.
格式
+ 使用`+=`添加单个元素
+ 使用`-=`删除单个元素
+ 使用`++=`追加一个数组到变长数组中
+ 使用`--=`移除变长数组中的指定多个元素
示例
1. 定义一个变长数组,包含以下元素: "hadoop", "spark", "flink"
2. 往该变长数组中添加一个"flume"元素
3. 从该变长数组中删除"hadoop"元素
4. 将一个包含"hive", "sqoop"元素的数组, 追加到变长数组中.
5. 从该变长数组中删除"sqoop", "spark"这两个元素.
6. 打印数组, 查看结果.
参考代码
//导包
import scala.collection.mutable.ArrayBuffer
//案例: 修改变长数组中的内容.
object ClassDemo03 {
def main(args: Array[String]): Unit = {
// 1. 定义一个变长数组,包含以下元素: "hadoop", "spark", "flink"
val arr = ArrayBuffer("hadoop", "spark", "flink")
// 2. 往该变长数组中添加一个"flume"元素
arr += "flume"
// 3. 从该变长数组中删除"hadoop"元素
arr -= "hadoop"
// 4. 将一个包含"hive", "sqoop"元素的数组, 追加到变长数组中.
arr ++= Array("hive", "sqoop")
// 5. 从该变长数组中删除"sqoop", "spark"这两个元素.
arr --= Array("sqoop", "spark")
// 6. 打印数组, 查看结果.
println(s"arr: $arr")
}
}
10.1.4 遍历数组
(1)概述
在Scala中, 可以使用以下两种方式来遍历数组:
1). 使用索引遍历数组中的元素
2). 使用for表达式直接遍历数组中的元素
(2)示例
1. 定义一个数组,包含以下元素1,2,3,4,
2. 通过两种遍历方式遍历数组,并打印数组中的元素
参考代码
//案例: 遍历数组
object ClassDemo04 {
def main(args: Array[String]): Unit = {
// 1. 定义一个数组,包含以下元素 1 , 2 , 3 , 4 , 5
val arr = Array(1, 2, 3, 4, 5)
// 2. 通过两种遍历方式遍历数组,并打印数组中的元素.
//方式一: 遍历索引的形式实现.
for (i <- 0 to arr.length - 1) println(arr(i))
println("-" * 15) //分割线
for (i <- 0 until arr.length) println(arr(i))
//方式二: 直接遍历数组元素.
println("-" * 15) //分割线
for (i <- arr) println(i)
//其他方式
println("-" * 15) //分割线
for (i <- arr.indices) println(arr(i))
}
}
> 注意:
0 until n 获取0~n之间的所有整数, 包含0, 不包含n.
0 to n 获取0~n之间的所有整数, 包含0, 也包含n.
10.1.5 数组常用算法
(1)概述
Scala中的数组封装了一些常用的计算操作,将来在对数据处理的时候,不需要我们自己再重新实现, 而是可以直接拿来用。以下为常用的几个算法:
-sum()方法: 求和
- max()方法: 求最大值
- min()方法: 求最小值
- sorted()方法: 排序, 返回一个新的数组.
- reverse()方法: 反转, 返回一个新的数组.
(2)需求
1. 定义一个数组, 包含4, 1, 6, 5, 2, 3这些元素.
2. 在main方法中, 测试上述的常用算法.
参考代码
//案例: 数组的常用算法
object ClassDemo05 {
def main(args: Array[String]): Unit = {
// 1. 定义一个数组, 包含 4 , 1 , 6 , 5 , 2 , 3 这些元素.
val arr = Array(4, 1, 6, 5, 2, 3)
// 2. 在main方法中, 测试上述的常用算法.
//测试sum
println(s"sum: ${arr.sum}")
//测试max
println(s"max: ${arr.max}")
//测试min
println(s"min: ${arr.min}")
//测试sorted
val arr2 = arr.sorted //即: arr 2 的内容为: 1 , 2 , 3 , 4 , 5 , 6
//测试reverse
val arr3 = arr.sorted.reverse //即: arr 3 的内容为: 6 , 5 , 4 , 3 , 2 , 1
// 3. 打印数组.
for (i <- arr) print(s"$i ")
println("\n" + "-" * 15)
for (i <- arr2) print(s"$i ")
println("\n" + "-" * 15)
for (i <- arr3) print(s"$i ")
}
}
10.2 元组
元组一般用来存储多个不同类型的值。例如同时存储姓名,年龄,性别,出生年月这些数据, 就要用到元组来存储了。并且元组的长度和元素都是不可变的。
10.2.1 格式
格式一: 通过小括号实现
val/var 元组 = (元素 1 , 元素 2 , 元素 3 ....)
格式二: 通过箭头来实现
val/var 元组 = 元素 1 -> 元素 2
注意: 上述这种方式, 只适用于元组中只有两个元素的情况.
10.2.2 示例
需求
1. 定义一个元组,包含学生的姓名和年龄.
2. 分别使用小括号以及箭头的方式来定义元组.
参考代码
//案例: 演示元组的定义格式
object ClassDemo06 {
def main(args: Array[String]): Unit = {
// 1. 定义一个元组,包含学生的姓名和年龄.
// 2. 分别使用小括号以及箭头的方式来定义元组.
val tuple1 = ("张三", 23)
val tuple2 = "张三" -> 23
println(tuple1)
println(tuple2)
}
}
10.2.3 访问元组中的元素
在Scala中, 可以通过元组名._编号的形式来访问元组中的元素,_1表示访问第一个元素,依次类推.
也可以通过元组名.productIterator的方式, 来获取该元组的迭代器, 从而实现遍历元组.
(1)格式
- 格式一: 访问元组中的单个元组
println(元组名._ 1 ) //打印元组的第一个元素.
println(元组名._ 2 ) //打印元组的第二个元组.
- 格式二: 遍历元组
val tuple1 = (值 1 , 值 2 , 值 3 , 值 4 , 值 5 ...) //可以有多个值
val it = tuple1.productIterator //获取当前元组的迭代器对象
for(i <- it) println(i) //打印元组中的所有内容.
(2)示例
1. 定义一个元组,包含一个学生的姓名和性别,"zhangsan", "male"
2. 分别获取该学生的姓名和性别, 并将结果打印到控制台上.
(3)参考代码
//案例: 获取元组中的元组.
object ClassDemo07 {
def main(args: Array[String]): Unit = {
// 1. 定义一个元组,包含一个学生的姓名和性别,"张三", "male"
val tuple1 = "张三" -> "male"
// 2. 分别获取该学生的姓名和性别
//方式一: 通过 _编号 的形式实现.
println(s"姓名: ${tuple1._1}, 性别: ${tuple1._2}")
//方式二: 通过迭代器遍历的方式实现.
//获取元组对应的迭代器对象.
val it = tuple1.productIterator
//遍历元组.
for (i <- it) println(i)
}
}
10.3 列表
列表(List)是Scala中最重要的, 也是最常用的一种数据结构。它存储的数据, 特点是:有序, 可重复。
在Scala中,列表分为两种, 即:不可变列表和可变列表。
(1)解释
1. 有序的意思并不是排序, 而是指元素的存入顺序和取出顺序是一致的.
2. 可重复的意思是列表中可以添加重复元素
10.3.1 不可变列表
(1)特点
不可变列表指的是: 列表的元素、长度都是不可变的。
(2)语法
格式一: 通过小括号直接初始化.
val/var 变量名 = List(元素 1 , 元素 2 , 元素 3 ...)
格式二: 通过Nil创建一个空列表.
val/var 变量名 = Nil
格式三: 使用::方法实现.
val/var 变量名 = 元素 1 :: 元素 2 :: Nil
注意: 使用::拼接方式来创建列表,必须在最后添加一个Nil
(3)示例
需求
1. 创建一个不可变列表,存放以下几个元素(1,2,3,4)
2. 使用Nil创建一个不可变的空列表
3. 使用::方法创建列表,包含-2、-1两个元素
参考代码
//案例: 演示不可变列表.
object ClassDemo08 {
def main(args: Array[String]): Unit = {
// 1. 创建一个不可变列表,存放以下几个元素( 1 , 2 , 3 , 4 )
val list1 = List(1, 2, 3, 4)
// 2. 使用`Nil`创建一个不可变的空列表
val list2 = Nil
// 3. 使用`::`方法创建列表,包含- 2 、- 1 两个元素
val list3 = -2 :: -1 :: Nil
// 4. 打印结果.
println(s"list 1 : $list1")
println(s"list 2 : $list2")
println(s"list 3 : $list3")
}
}
10.3.2 可变列表
(1)特点
可变列表指的是列表的元素、长度都是可变的.
(2)语法
要使用可变列表, 必须先导包.
import scala.collection.mutable.ListBuffer
小技巧: 可变集合都在mutable包中, 不可变集合都在immutable包中(默认导入).
格式一: 创建空的可变列表.
val/var 变量名 = ListBuffer[数据类型]()
格式二: 通过小括号直接初始化.
val/var 变量名 = ListBuffer(元素 1 ,元素 2 ,元素 3 ...)
(3)
需求
1. 创建空的整形可变列表.
2. 创建一个可变列表,包含以下元素:1,2,3,
参考代码
//案例: 演示可变列表.
// 1. 导包
import scala.collection.mutable.ListBuffer
object ClassDemo09 {
def main(args: Array[String]): Unit = {
// 2. 创建空的整形可变列表.
val list1 = new ListBuffer[Int]()
// 3. 创建一个可变列表,包含以下元素: 1 , 2 , 3 , 4
val list2 = ListBuffer(1, 2, 3, 4)
println(s"list 1 : $list1")
println(s"list 2 : $list2")
}
}
(4)可变列表的常用操作
关于可变列表的常见操作如下:
格式 | 功能 |
列表名(索引) | 根据索引(索引从0开始),获取列表中的指定元素. |
列表名(索引)= 值 | 修改元素值 |
+= | 往列表中添加单个元素 |
++= | 往列表中追加一个列表 |
-= | 删除列表中的某个指定元素 |
--= | 以列表的形式,删除列表中的多个元素. |
toList | 将可变列表(ListBuffer)转换为不可变列表(List) |
toArray | 将可变列表(ListBuffer)转换为数组 |
示例
1. 定义一个可变列表包含以下元素:1,2,
2. 获取第一个元素, 并打印结果到控制台.
3. 添加一个新的元素: 4
4. 追加一个列表,该列表包含以下元素:5,6,
5. 删除元素 7
6. 删除元素3, 4
7. 将可变列表转换为不可变列表
8. 将可变列表转换为数组
9. 打印结果.
参考代码
//案例: 演示可变列表的常见操作.
import scala.collection.mutable.ListBuffer
object ClassDemo10 {
def main(args: Array[String]): Unit = {
// 1. 定义一个可变列表包含以下元素: 1 , 2 , 3
val list1 = ListBuffer(1, 2, 3)
// 2. 获取第一个元素, 并打印结果到控制台.
println(list1(0))
// 3. 添加一个新的元素: 4
list1 += 4
// 4. 追加一个列表,该列表包含以下元素: 5 , 6 , 7
list1 ++= List(5, 6, 7)
// 5. 删除元素 7
list1 -= 7
// 6. 删除元素 3 , 4
list1 --= List(3, 4)
// 7. 将可变列表转换为不可变列表
val list2 = list1.toList
// 8. 将可变列表转换为数组
val arr = list1.toArray
// 9. 打印结果.
println(s"list 1 : $list1")
println(s"list 2 : $list2")
println(s"arr: $arr")
}
}
10.3.3 列表的常用操作
(1)格式详解
在实际开发中, 我们经常要操作列表, 以下列举的是列表的常用的操作:
格式 | 功能 |
isEmpty | 判断列表是否为空 |
++ | 拼接两个列表,返回一个新的列表 |
head | 获取列表的首个元素 |
tail | 获取列表中除首个元素之外,其他所有的元素 |
reverse | 对列表进行反转,返回一个新的列表 |
take | 获取列表中的前缀元素(具体个数可以自定义) |
drop | 获取列表中的后缀元素(具体个数可以自定义) |
flatten | 对列表进行扁平化操作,返回一个新的列表 |
zip | 对列表进行拉链操作,即:将两个列表合并成一个列表 |
unzip | 对列表进行拉开操作,即:将一个列表拆解成两个列表 |
toString | 将列表转换成其对应的默认字符串形式 |
mkString | 将列表转换成其对应的指定字符串形式 |
union | 获取两个列表的并集元素,并返回一个新的列表 |
intersect | 获取两个列表的交集元素,并返回一个新的列表 |
diff | 获取两个列表的差集元素,并返回一个新的列表 |
#### 3.3.2 示例一: 基础操作
##### 需求
1. 定义一个列表list1,包含以下元素:1,2,3,
2. 使用isEmpty方法判断列表是否为空, 并打印结果.
3. 再定义一个列表list2,包含以下元素: 4,5,
4. 使用++将两个列表拼接起来, 并打印结果.
5. 使用head方法,获取列表的首个元素, 并打印结果.
6. 使用tail方法,获取列表中除首个元素之外, 其他所有的元素, 并打印结果.
7. 使用reverse方法将列表的元素反转, 并打印反转后的结果.
8. 使用take方法获取列表的前缀元素, 并打印结果.
9. 使用drop方法获取列表的后缀元素, 并打印结果.
##### 参考代码
```scala
//案例: 演示列表的基础操作.
object ClassDemo11 {
def main(args: Array[String]): Unit = {
// 1. 定义一个列表list 1 ,包含以下元素: 1 , 2 , 3 , 4
val list1 = List(1, 2, 3, 4)
// 2. 使用isEmpty方法判断列表是否为空, 并打印结果.
println(s"isEmpty: ${list1.isEmpty}")
// 3. 再定义一个列表list 2 ,包含以下元素: 4 , 5 , 6
val list2 = List(4, 5, 6)
// 4. 使用`++`将两个列表拼接起来, 并打印结果.
val list3 = list1 ++ list2
println(s"list 3 : $list3")
// 5. 使用head方法,获取列表的首个元素, 并打印结果.
println(s"head: ${list3.head}")
// 6. 使用tail方法,获取列表中除首个元素之外, 其他所有的元素, 并打印结果.
println(s"tail: ${list3.tail}")
// 7. 使用reverse方法将列表的元素反转, 并打印反转后的结果.
val list4 = list3.reverse
println(s"list 4 : $list4")
// 8. 使用take方法获取列表的前缀元素(前三个元素), 并打印结果.
println(s"take: ${list3.take(3)}")
// 9. 使用drop方法获取列表的后缀元素(除前三个以外的元素), 并打印结果.
println(s"drop: ${list3.drop(3)}")
}
}
```
#### 3.3.3 示例二: 扁平化(压平)
##### 概述
扁平化表示将嵌套列表中的所有具体元素单独的放到一个新列表中. 如下图:
> 注意: 如果某个列表中的所有元素都是列表, 那么这样的列表就称之为: 嵌套列表.
![扁平化](C:\Users\han\Desktop\大数据\笔记\5、scala\3、\pictures\扁平化.png)
##### 需求
1. 定义一个列表, 该列表有三个元素, 分别为:List(1,2)、List(3)、List(4,5)
2. 使用flatten将这个列表转换为List(1,2,3,4,5)
3. 打印结果.
##### 参考代码
```scala
//案例: 演示扁平化操作.
object ClassDemo12 {
def main(args: Array[String]): Unit = {
// 1. 定义一个列表, 该列表有三个元素, 分别为:List( 1 , 2 )、List( 3 )、List( 4 , 5 )
val list1 = List(List(1, 2), List(3), List(4, 5))
// 2. 使用flatten将这个列表转换为List( 1 , 2 , 3 , 4 , 5 )
val list2 = list1.flatten
// 3. 打印结果
println(list2)
}
}
```
#### 3.3.4 示例三: 拉链与拉开
##### 概述
- 拉链:将两个列表,组合成一个元素为元组的列表
> 解释: 将列表List("张三", "李四"), List(23, 24)组合成列表List((张三,23), (李四,24))
- 拉开:将一个包含元组的列表,拆解成包含两个列表的元组
> 解释: 将列表List((张三,23), (李四,24))拆解成元组(List(张三, 李四),List(23, 24))
##### 需求
1. 定义列表names, 保存三个学生的姓名,分别为:张三、李四、王五
2. 定义列表ages, 保存三个学生的年龄,分别为:23, 24, 25
3. 使用zip将列表names和ages, 组合成一个元素为元组的列表list
4. 使用unzip将列表list1拆解成包含两个列表的元组tuple
5. 打印结果
##### 参考代码
```scala
//案例: 演示拉链与拉开
object ClassDemo13 {
def main(args: Array[String]): Unit = {
// 1. 定义列表names, 保存三个学生的姓名,分别为:张三、李四、王五
val names = List("张三", "李四", "王五")
// 2. 定义列表ages, 保存三个学生的年龄,分别为: 23 , 24 , 25
val ages = List(23, 24, 25)
// 3. 使用zip将列表names和ages, 组合成一个元素为元组的列表list1.
val list1 = names.zip(ages)
// 4. 使用unzip将列表list 1 拆解成包含两个列表的元组tuple1
val tuple1 = list1.unzip
// 5. 打印结果
println("拉链: " + list1)
println("拉开: " + tuple1)
}
}
```
#### 3.3.5 示例四: 列表转字符串
##### 概述
将列表转换成其对应的字符串形式, 可以通过toString方法或者mkString方法实现, 其中
- `toString`方法: 可以返回List中的所有元素
- `mkString`方法: 可以将元素以指定分隔符拼接起来。
> 注意: 默认没有分隔符.
##### 需求
1. 定义一个列表,包含元素:1,2,3,
2. 使用toString方法输出该列表的元素
3. 使用mkString方法, 用冒号将元素都拼接起来, 并打印结果.
##### 参考代码
```scala
//案例: 演示将列表转成其对应的字符串形式.
object ClassDemo14 {
def main(args: Array[String]): Unit = {
// 1. 定义一个列表,包含元素: 1 , 2 , 3 , 4
val list1 = List(1, 2, 3, 4)
// 2. 使用toString方法输出该列表的元素
println(list1.toString)
//简写形式, 因为: 输出语句打印对象, 默认调用了该对象的toString()方法
println(list1)
println("-" * 15)
// 3. 使用mkString方法, 用冒号将元素都拼接起来, 并打印结果.
println(list1.mkString(" : "))
}
}
```
#### 3.3.6 示例五: 并集, 交集, 差集
##### 概述
操作数据时, 我们可能会遇到求并集, 交集, 差集的需求, 这是时候就要用到union, intersect, diff这些方法了, 其中
- `union`: 表示对两个列表取并集,而且不去重
> 例如: list1.union(list2), 表示获取list1和list2中所有的元素(元素不去重).
>
> 如果想要去除重复元素, 则可以通过distinct实现.
>
- `intersect`: 表示对两个列表取交集
> 例如: list1.intersect(list2), 表示获取list1, list2中都有的元素.
>
- `diff`: 表示对两个列表取差集.
> 例如:list1.diff(list2),表示获取list1中有, 但是list2中没有的元素.
>
##### 需求
1. 定义列表list1,包含以下元素:1,2,3,
2. 定义列表list2,包含以下元素:3,4,5,
3. 使用union获取这两个列表的并集
4. 在第三步的基础上, 使用distinct去除重复的元素
5. 使用intersect获取列表list1和list2的交集
6. 使用diff获取列表list1和list2的差集
7. 打印结果
##### 参考代码
```scala
//案例: 演示获取并集, 交集, 差集.
object ClassDemo15 {
def main(args: Array[String]): Unit = {
// 1. 定义列表list 1 ,包含以下元素: 1 , 2 , 3 , 4
val list1 = List(1, 2, 3, 4)
// 2. 定义列表list 2 ,包含以下元素: 3 , 4 , 5 , 6
val list2 = List(3, 4, 5, 6)
// 3. 使用union获取这两个列表的并集
val unionList = list1.union(list2)
// 4. 在第三步的基础上, 使用distinct去除重复的元素
val distinctList = unionList distinct
// 5. 使用intersect获取列表list 1 和list 2 的交集
val intersectList = list1.intersect(list2)
// 6. 使用diff获取列表list 1 和list 2 的差集
val diffList = list1.diff(list2)
// 7. 打印结果
println("并集, 不去重: " + unionList)
println("并集, 去重: " + distinctList)
println("交集: " + intersectList)
println("差集: " + diffList)
}
}
```
10.4 集
10.4.1 概述
Set(也叫: 集)代表没有重复元素的集合。特点是: **唯一, 无序**
Scala中的集分为两种,一种是不可变集,另一种是可变集。
> 解释:
>
> 1. 唯一的意思是Set中的元素具有唯一性, 没有重复元素
> 2. 无序的意思是Set集中的元素, 添加顺序和取出顺序不一致
10.4.2 不可变集
> 不可变集指的是元素, 集的长度都不可变.
(1)语法
- 格式一: 创建一个空的不可变集
```scala
val/var^ 变量名^ =^ Set[类型]()
```
- 格式二: 给定元素来创建一个不可变集
```scala
val/var^ 变量名^ =^ Set(元素^1 ,^ 元素^2 ,^ 元素^3 ...)
```
#### 4.2.2 示例一: 创建不可变集
##### 需求
1. 定义一个空的整型不可变集.
2. 定义一个不可变集,保存以下元素:1,1,3,2,4,8.
3. 打印结果.
##### 参考代码
```scala
//案例: 演示不可变集.
object ClassDemo16 {
def main(args: Array[String]): Unit = {
// 1. 定义一个空的整型不可变集.
val set1 = Set[Int]()
// 2. 定义一个不可变集,保存以下元素: 1 , 1 , 3 , 2 , 4 , 8.
val set2 = Set(1, 1, 3, 2, 4, 8)
// 3. 打印结果.
println(s"set 1 : $set1")
println(s"set 2 : $set2")
}
}
```
#### 4.2.3 示例二: 不可变集的常见操作
##### 格式
1. 获取集的大小(size)
2. 遍历集(和遍历数组一致)
3. 添加一个元素,生成一个新的Set(+)
4. 拼接两个集,生成一个新的Set(++)
5. 拼接集和列表,生成一个新的Set(++)
> 注意:
>
> 1. -(减号)表示删除一个元素, 生成一个新的Set
> 2. --表示批量删除某个集中的元素, 从而生成一个新的Set
##### 需求
1. 创建一个集,包含以下元素:1,1,2,3,4,
2. 获取集的大小, 并打印结果.
3. 遍历集,打印每个元素.
4. 删除元素 1 ,生成新的集, 并打印.
5. 拼接另一个集Set(6, 7, 8), 生成新的集, 并打印.
6. 拼接一个列表List(6,7,8, 9), 生成新的集, 并打印.
##### 参考代码
```scala
//案例: 演示不可变集的常用操作.
object ClassDemo17 {
def main(args: Array[String]): Unit = {
// 1. 创建一个集,包含以下元素: 1 , 1 , 2 , 3 , 4 , 5
val set1 = Set(1, 1, 2, 3, 4, 5)
// 2. 获取集的大小
println("set1 的长度为: " + set1.size)
// 3. 遍历集,打印每个元素
println("set1 集中的元素为: ")
for (i <- set1) println(i)
println("-" * 15)
// 4. 删除元素 1 ,生成新的集
val set2 = set1 - 1
println("set2 : " + set2)
// 5. 拼接另一个集( 6 , 7 , 8 )
val set3 = set1 ++ Set(6, 7, 8)
println("set3 : " + set3)
// 6. 拼接一个列表( 6 , 7 , 8 , 9 )
val set4 = set1 ++ List(6, 7, 8, 9)
println("set4 : " + set4)
}
}
```
10.4.3 可变集
(1) 概述
可变集指的是元素, 集的长度都可变, 它的创建方式和不可变集的创建方式一致,只不过需要先导入可变集类。
手动导入:
import scala.collection.mutable.Set
(2)示例
##### 需求
1. 定义一个可变集,包含以下元素: 1,2,3, 4
2. 添加元素 5 到可变集中
3. 添加元素6, 7, 8到可变集中
4. 从可变集中移除元素 1
5. 从可变集中移除元素3, 5, 7
6. 打印结果.
##### 参考代码
```scala
import scala.collection.mutable
//案例: 演示可变集.
object ClassDemo18 {
def main(args: Array[String]): Unit = {
// 1. 定义一个可变集,包含以下元素: 1 , 2 , 3 , 4
val set1 = mutable.Set(1, 2, 3, 4)
// 2. 添加元素 5 到可变集中
set1 += 5
// 3. 添加元素 6 , 7 , 8 到可变集中
//set 1 ++= Set( 6 , 7 , 8 )
set1 ++= List(6, 7, 8) //两种写法均可.
// 4. 从可变集中移除元素 1
set1 -= 1
// 5. 从可变集中移除元素 3 , 5 , 7
//set 1 - -= Set( 3 , 5 , 7 )
set1 --= List(3, 5, 7) //两种写法均可.
// 6. 打印结果.
println(set1)
}
}
10.5 映射
映射指的就是Map。它是由键值对(key, value)组成的集合。特点是: **键具有唯一性**, **但是值可以重复**. 在Scala中,Map也分为不可变Map和可变Map。
> 注意: 如果添加重复元素(即: 两组元素的键相同), 则会用新值覆盖旧值.
10.5.1 不可变Map
不可变Map指的是元素,长度都不可变.
语法
- 方式一: 通过箭头的方式实现.
val/var map = Map(键->值, 键->值, 键->值...) // 推荐,可读性更好
- 方式二: 通过小括号的方式实现.
val/var^ map^ =^ Map((键,^ 值),^ (键,^ 值),^ (键,^ 值),^ (键,^ 值)...)
需求
1. 定义一个映射,包含以下学生姓名和年龄数据: 张三 -> 23, 李四 -> 24, 李四 -> 40
2. 打印结果.
参考代码
//案例: 演示不可变Map
object ClassDemo19 {
def main(args: Array[String]): Unit = {
// 1. 定义一个映射,包含以下学生姓名和年龄数据.
val map1 = Map("张三" -> 23, "李四" -> 24, "李四" -> 40)
val map2 = Map(("张三", 23), ("李四", 24), ("李四", 40))
// 2. 打印结果.
println(s"map 1 : $map1")
println(s"map 2 : $map2")
}
}
10.5.2 可变Map
特点
可变Map指的是元素, 长度都可变. 定义语法与不可变Map一致, 只不过需要先手动导包:
import scala.collection.mutable.Map
需求
1. 定义一个映射,包含以下学生姓名和年龄数据: 张三 -> 23, 李四 -> 24
2. 修改张三的年龄为 30
3. 打印结果
参考代码
import scala.collection.mutable
//案例: 演示可变Map.
object ClassDemo20 {
def main(args: Array[String]): Unit = {
// 1. 定义一个映射,包含以下学生姓名和年龄数据.
val map1 = mutable.Map("张三" -> 23, "李四" -> 24)
val map2 = mutable.Map(("张三", 23), ("李四", 24))
// 2. 修改张三的年龄为 30
map1("张三") = 30
// 3. 打印结果
println(s"map 1 : $map1")
println(s"map 2 : $map2")
}
}
10.5.3 Map基本操作
#### 格式
1. `map(key)`: 根据键获取其对应的值, 键不存在返回None.
2. `map.keys`: 获取所有的键.
3. `map.values`: 获取所有的值.
4. 遍历map集合: 可以通过普通for实现.
5. `getOrElse`: 根据键获取其对应的值, 如果键不存在, 则返回指定的默认值.
6. `+号`: 增加键值对, 并生成一个新的Map.
> 注意: 如果是可变Map, 则可以通过+=或者++=直接往该可变Map中添加键值对元素.
7. `-号`: 根据键删除其对应的键值对元素, 并生成一个新的Map.
> 注意: 如果是可变Map, 则可以通过-=或者--=直接从该可变Map中删除键值对元素.
示例
1. 定义一个映射,包含以下学生姓名和年龄数据: 张三 -> 23, 李四 -> 24
2. 获取张三的年龄, 并打印.
3. 获取所有的学生姓名, 并打印.
4. 获取所有的学生年龄, 并打印.
5. 打印所有的学生姓名和年龄.
6. 获取王五的年龄,如果王五不存在,则返回-1, 并打印.
7. 新增一个学生:王五, 25, 并打印结果.
8. 将李四从可变映射中移除, 并打印.
参考代码
import scala.collection.mutable
//案例: 演示Map的常见操作.
object ClassDemo21 {
def main(args: Array[String]): Unit = {
// 1. 定义一个映射,包含以下学生姓名和年龄数据: 张三 - > 23 , 李四 - > 24
val map1 = mutable.Map("张三" -> 23, "李四" -> 24)
// 2. 获取张三的年龄, 并打印.
println(map1.get("张三"))
// 3. 获取所有的学生姓名, 并打印.
println(map1.keys)
// 4. 获取所有的学生年龄, 并打印.
println(map1.values)
// 5. 打印所有的学生姓名和年龄.
for ((k, v) <- map1) println(s"键:$k, 值:$v")
println("-" * 15)
// 6. 获取`王五`的年龄,如果`王五`不存在,则返回- 1 , 并打印.
println(map1.getOrElse("王五", -1))
println("-" * 15)
// 7. 新增一个学生:王五, 25 , 并打印结果.
/*
不可变Map
val map 2 = map1 + ("王五" -> 25 )
println(s"map 1 : $map1")
println(s"map 2 : $map2")
*/
map1 += "王五" -> 25
println(s"map 1 : $map1")
// 8. 将`李四`从可变映射中移除, 并打印.
map1 -= "李四"
println(s"map 1 : $map1")
}
}
10. 6. 迭代器(iterator)
10.6.1 概述
Scala针对每一类集合都提供了一个迭代器(iterator), 用来迭代访问集合.
10.6.2 注意事项
1. 使用iterator方法可以从集合获取一个迭代器.
> 迭代器中有两个方法:
hasNext方法: 查询容器中是否有下一个元素
next方法: 返回迭代器的下一个元素,如果没有,抛出NoSuchElementException
2. 每一个迭代器都是有状态的.
即: 迭代完后保留在最后一个元素的位置. 再次使用则抛出NoSuchElementException
3. 可以使用while或者for来逐个获取元素.
10.6.3 示例
#### 需求
1. 定义一个列表,包含以下元素:1,2,3,4,5
2. 使用while循环和迭代器,遍历打印该列表.
#### 参考代码
//案例: 演示迭代器
object ClassDemo22 {
def main(args: Array[String]): Unit = {
// 1. 定义一个列表,包含以下元素: 1 , 2 , 3 , 4 , 5
val list1 = List(1, 2, 3, 4, 5)
// 2. 使用while循环和迭代器,遍历打印该列表.
// 2. 1 根据列表获取其对应的迭代器对象.
val it = list1.iterator
// 2. 2 判断迭代器中是否有下一个元素.
while (it.hasNext)
// 2. 3 如果有, 则获取下一个元素, 并打印.
println(it.next)
//分割线.
println("-" * 15)
//迭代完后, 再次使用该迭代器获取元素, 则抛异常: NoSuchElementException
println(it.next)
}
}
10.7. 函数式编程
- 所谓的函数式编程指定就是方法的参数列表可以接收函数对象.
- 例如: add(10, 20)就不是函数式编程, 而add(函数对象)这种格式就叫函数式编程.
- 我们将来编写Spark/Flink的大量业务代码时, 都会使用到函数式编程。下面的这些操作是学习的重点。
| 函数名 | 功能 |
| -------- | ------------------------------ |
| foreach | 用来遍历集合的 |
| map | 用来对集合进行转换的 |
| flatmap | 用来对集合进行映射扁平化操作 |
| filter | 用来过滤出指定的元素 |
| sorted | 用来对集合元素进行默认排序 |
| sortBy | 用来对集合按照指定字段排序 |
| sortWith | 用来对集合进行自定义排序 |
| groupBy | 用来对集合元素按照指定条件分组 |
| reduce | 用来对集合元素进行聚合计算 |
| fold | 用来对集合元素进行折叠计算 |
10.7.1遍历(foreach)
示例一:foreach
采用foreach来遍历集合, 可以让代码看起来更简洁, 更优雅.
格式
def foreach(f:(A) => Unit): Unit
//简写形式
def foreach(函数)
#### 说明
| foreach | API | 说明 |
| ------- | ------------- | ---------------------------------------------------- |
| 参数 | f: (A) ⇒ Unit | 接收一个函数对象, 函数的参数为集合的元素,返回值为空 |
| 返回值 | Unit | 表示foreach函数的返回值为: 空 |
#### 执行过程
![foreach](pictures/foreach.png)
#### 需求
有一个列表,包含以下元素1,2,3,4,请使用foreach方法遍历打印每个元素
#### 参考代码
```scala
//案例: 演示foreach函数
object ClassDemo23 {
def main(args: Array[String]): Unit = {
// 1. 定义一个列表, 包含 1 , 2 , 3 , 4
val list1 = List(1, 2, 3, 4)
// 2. 通过foreach函数遍历上述的列表.
//x:表示集合中的每个元素 函数体表示输出集合中的每个元素.
list1.foreach((x:Int) => {println(x)})
println("*" * 15)
//简写版本
list1.foreach(x => println(x))
}
}
示例二: 简化函数定义
#### 概述
上述案例函数定义有点啰嗦,我们有更简洁的写法。可以通过如下两种方式来简化函数定义:
方式一: 通过类型推断来简化函数定义.
解释:因为使用foreach来迭代列表,而列表中的每个元素类型是确定的, 所以我们可以通过类型推断让Scala程序来自动推断出来集合中每个元素参数的类型, 即: 在我们创建函数时,可以省略其参数列表的类型.
- 方式二: 通过下划线来简化函数定义.
> 解释:
>
> 当函数参数,只在函数体中出现一次,而且函数体没有嵌套调用时,可以使用下划线来简化函数定义.
#### 示例
1. 有一个列表,包含元素1,2,3,4,请使用foreach方法遍历打印每个元素.
2. 使用类型推断来简化函数定义.
3. 使用下划线来简化函数定义
#### 参考代码
```scala
//案例: 演示简化函数定义.
object ClassDemo24 {
def main(args: Array[String]): Unit = {
// 1. 有一个列表,包含元素 1 , 2 , 3 , 4 ,请使用foreach方法遍历打印每个元素.
val list1 = List(1, 2, 3, 4)
list1.foreach((x: Int) => println(x))
println("-" * 15)
// 2. 使用类型推断来简化函数定义.
list1.foreach(x => println(x))
println("-" * 15)
// 3. 使用下划线来简化函数定义
//当函数参数,只在函数体中出现一次,而且函数体没有嵌套调用时,可以使用下划线来简化函数定义.
list1.foreach(println(_))
}
}
10.7.2映射(map)
集合的映射操作是指`将一种数据类型转换为另外一种数据类型的过程` , 它是在进行数据计算的时候, 甚至将来在编写Spark/Flink程序时用得最多的操作,也是我们必须要掌握的。
> 例如: 把List[Int]转换成List[String].
#### 格式
```scala
def map[B](f: (A) ⇒ B): TraversableOnce[B]
//简写形式:
def map(函数对象)
```
#### 说明
| map方法 | API | 说明 |
| ------- | ------------------ | ------------------------------------------------------------ |
| 泛型 | [B] | 指定map方法最终返回的集合泛型, 可省略不写 |
| 参数 | f: (A) ⇒ B | 函数对象, 参数列表为类型A(要转换的列表元素),返回值为类型B |
| 返回值 | TraversableOnce[B] | B类型的集合, 可省略不写. |
#### 执行过程
![映射](pictures/映射.png)
#### 需求
1. 创建一个列表,包含元素1,2,3,4
2. 将上述的数字转换成对应个数的*, 即: 1变为*, 2变为**, 以此类推.
#### 参考代码
```scala
//案例: 演示map函数(映射)
object ClassDemo25 {
def main(args: Array[String]): Unit = {
// 1. 创建一个列表,包含元素 1 , 2 , 3 , 4
val list1 = List(1, 2, 3, 4)
// 2. 将上述的数字转换成对应个数的`*`, 即: 1 变为*, 2 变为**, 以此类推.
//方式一: 普通写法
val list2 = list1.map((x: Int) => "*" * x)
println(s"list 2 : $list2")
//方式二: 通过类型推断实现.
val list3 = list1.map(x => "*" * x)
println(s"list 3 : $list3")
//方式三: 通过下划线实现.
val list4 = list1.map("*" * _)
println(s"list 4 : $list4")
}
}
```
10.7.3扁平化映射(flatMap)
扁平化映射可以理解为先map,然后再flatten, 它也是将来用得非常多的操作,也是必须要掌握的, 如图:
![扁平化映射](pictures/扁平化映射.png)
> 解释:
>
> 1. map是将列表中的元素转换为一个List
>
> 2. flatten再将整个列表进行扁平化
#### 格式
```scala
def flatMap[B](f:(A) => GenTraversableOnce[B]): TraversableOnce[B]
//简写形式:
def flatMap(f:(A) => 要将元素A转换成的集合B的列表)
```
#### 说明
| flatmap方法 | API | 说明 |
| ----------- | ------------------------------ | ------------------------------------------------------------ |
| 泛型 | [B] | 最终要返回的集合元素类型, 可省略不写. |
| 参数 | f: (A) ⇒ GenTraversableOnce[B] | 传入一个函数对象<br />函数的参数是集合的元素<br />函数的返回值是一个集合 |
| 返回值 | TraversableOnce[B] | B类型的集合 |
#### 示例
#### 需求
1. 有一个包含了若干个文本行的列表:"hadoop hive spark flink flume", "kudu hbase sqoop storm"
2. 获取到文本行中的每一个单词,并将每一个单词都放到列表中.
#### 思路分析
![思路分析](pictures/思路分析.png)
#### 参考代码
```scala
//案例: 演示映射扁平化(flatMap)
object ClassDemo26 {
def main(args: Array[String]): Unit = {
// 1. 有一个包含了若干个文本行的列表:"hadoop hive spark flink flume", "kudu hbase sqoop storm"
val list1 = List("hadoop hive spark flink flume", "kudu hbase sqoop storm")
// 2. 获取到文本行中的每一个单词,并将每一个单词都放到列表中.
//方式一: 通过map, flatten实现.
val list2 = list1.map(_.split(" "))
val list3 = list2.flatten
println(s"list 3 : $list3")
//方式二: 通过flatMap实现.
val list4 = list1.flatMap(_.split(" "))
println(s"list 4 : $list4")
}
}
```
10.7.4过滤(filter)
过滤指的是过滤出(筛选出)符合一定条件的元素.
#### 格式
```scala
def filter(f:(A) => Boolean): TraversableOnce[A]
//简写形式:
def filter(f:(A) => 筛选条件)
```
#### 说明
| filter方法 | API | 说明 |
| ---------- | ------------------ | ------------------------------------------------------------ |
| 参数 | f: (A) ⇒ Boolean | 传入一个函数对象<br />接收一个集合类型的参数<br />返回布尔类型,满足条件返回true, 不满足返回false |
| 返回值 | TraversableOnce[A] | 符合条件的元素列表 |
#### 执行过程
![filter](pictures/filter.png)
#### 案例
1. 有一个数字列表,元素为:1,2,3,4,5,6,7,8,9
2. 请过滤出所有的偶数
#### 参考代码
```scala
//案例: 演示过滤(filter)
object ClassDemo27 {
def main(args: Array[String]): Unit = {
// 1. 有一个数字列表,元素为: 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9
val list1 = (1 to 9).toList
// 2. 请过滤出所有的偶数
val list2 = list1.filter(_ % 2 == 0)
println(s"list 2 : $list2")
}
}
```
10.7.5排序(sorted、sortBy、sortWith)
在scala集合中,可以使用以下三种方式来进行排序:
| 函数名 | 功能 |
| -------- | -------------------------- |
| sorted | 用来对集合元素进行默认排序 |
| sortBy | 用来对集合按照指定字段排序 |
| sortWith | 用来对集合进行自定义排序 |
(1)默认排序(sorted)
所谓的默认排序指的是对列表元素按照升序进行排列. 如果需要降序排列, 则升序后, 再通过reverse实现.
#### 需求
1. 定义一个列表,包含以下元素: 3, 1, 2, 9, 7
2. 对列表进行升序排序
3. 对列表进行降序排列.
#### 参考代码
```scala
//案例: 演示默认排序(sorted)
object ClassDemo28 {
def main(args: Array[String]): Unit = {
// 1. 定义一个列表,包含以下元素: 3 , 1 , 2 , 9 , 7
val list1 = List(3, 1, 2, 9, 7)
// 2. 对列表进行升序排序
val list2 = list1.sorted
println(s"list 2 : $list2")
// 3. 对列表进行降序排列.
val list3 = list2.reverse
println(s"list 3 : $list3")
}
}
```
```c
#include <stdio.h>
void main(){
int num;
float money,price;
printf("请输入货物数量:");
scarf("%d",&num);
printf("货物总价为:");
price=2.5;
money=mm*price;
prirtf("money=%.1f元",money);
}
```
(2)指定字段排序(sortBy)
所谓的指定字段排序是指对列表元素根据传入的函数转换后,再进行排序.
> 例如: 根据列表List("01 hadoop", "02 flume")的 字母进行排序.
#### 格式
```scala
def sortBy[B](f:(A) => B): List[A]
//简写形式:
def sortBy(函数对象)
```
#### 说明
| sortBy方法 | API | 说明 |
| ---------- | ---------- | ------------------------------------------------------------ |
| 泛型 | [B] | 排序字段的数据类型. |
| 参数 | f: (A) ⇒ B | 传入函数对象<br />接收一个集合类型的元素参数<br />返回B类型的元素进行排序 |
| 返回值 | List[A] | 返回排序后的列表 |
#### 示例
1. 有一个列表,分别包含几下文本行:"01 hadoop", "02 flume", "03 hive", "04 spark"
2. 请按照单词字母进行排序
#### 参考代码
```scala
//案例: 演示根据指定字段排序(sortBy)
object ClassDemo29 {
def main(args: Array[String]): Unit = {
// 1. 有一个列表,分别包含几下文本行:" 01 hadoop", " 02 flume", " 03 hive", " 04 spark"
val list1 = List(" 01 hadoop", " 02 flume", " 03 hive", " 04 spark")
// 2. 请按照单词字母进行排序
//val list 2 = list 1 .sortBy(x => x.split(" ")( 1 ))
//简写形式:
val list2 = list1.sortBy(_.split(" ")(1))
println(s"list 2 : $list2")
}
}
```
(3)自定义排序(sortWith)
所谓的自定义排序指的是根据一个自定义的函数(规则)来进行排序.
#### 格式
```scala
def sortWith(f: (A, A) => Boolean): List[A]
//简写形式:
def sortWith(函数对象: 表示自定义的比较规则)
```
#### 说明
| sortWith方法 | API | 说明 |
| ------------ | ------------------- | ------------------------------------------------------------ |
| 参数 | f: (A, A) ⇒ Boolean | 传入一个比较大小的函数对象<br />接收两个集合类型的元素参数<br />返回两个元素大小,小于返回true,大于返回false |
| 返回值 | List[A] | 返回排序后的列表 |
#### 示例
1. 有一个列表,包含以下元素:2,3,1,6,4,5
2. 使用sortWith对列表进行降序排序
#### 参考代码
```scala
//案例: 演示自定义排序(sortWith)
object ClassDemo30 {
def main(args: Array[String]): Unit = {
// 1. 有一个列表,包含以下元素: 2 , 3 , 1 , 6 , 4 , 5
val list1 = List(2, 3, 1, 6, 4, 5)
// 2. 使用sortWith对列表进行降序排序
var list2 = list1.sortWith((x, y) => x > y) //降序
//简写形式:
//第一个下划线相当于上面的: x
//第二个下划线相当于上面的: y
list2 = list1.sortWith(_ > _) //降序
println(s"list 2 : $list2")
}
}
```
10.7.6分组(groupBy)
分组指的是将数据按照指定条件进行分组, 从而方便我们对数据进行统计分析.
#### 格式
```scala
def groupBy[K](f:(A) => K): Map[K, List[A]]
//简写形式:
def groupBy(f:(A) => 具体的分组代码)
```
#### 说明
| groupBy方法 | API | 说明 |
| ----------- | --------------- | ------------------------------------------------------------ |
| 泛型 | [K] | 分组字段的类型 |
| 参数 | f: (A) ⇒ K | 传入一个函数对象<br />接收集合元素类型的参数<br />按照K类型的key进行分组,相同的key放在一组中, 并返回结果. |
| 返回值 | Map[K, List[A]] | 返回一个映射,K为分组字段,List为这个分组字段对应的一组数据 |
#### 执行过程
![分组](pictures/分组.png)
#### 需求
1. 有一个列表,包含了学生的姓名和性别: "刘德华" -> "男", "刘亦菲" -> "女", "胡歌" -> "男"
2. 请按照性别进行分组.
3. 统计不同性别的学生人数.
#### 参考代码
```scala
//案例: 演示分组函数(groupBy)
object ClassDemo31 {
def main(args: Array[String]): Unit = {
// 1. 有一个列表,包含了学生的姓名和性别: "刘德华" - > "男", "刘亦菲" - > "女", "胡歌" - > "男"
val list1 = List("刘德华" -> "男", "刘亦菲" -> "女", "胡歌" -> "男")
// 2. 请按照性别进行分组.
//val list 2 = list 1 .groupBy(x => x._ 2 )
//简写形式
val list2 = list1.groupBy(_._2)
//println(s"list 2 : ${list 2 }")
// 3. 统计不同性别的学生人数.
val list3 = list2.map(x => x._1 -> x._2.size)
println(s"list3 : $list3")
}
}
```
10.7.7聚合操作
所谓的聚合操作指的是将一个列表中的数据合并为一个. 这种操作经常用来统计分析中. 常用的聚合操作主要有两个:
- reduce: 用来对集合元素进行聚合计算
- fold: 用来对集合元素进行折叠计算
(1)聚合(reduce)
reduce表示将列表传入一个函数进行聚合计算.
#### 格式
```scala
def reduce[A 1 >: A](op:(A 1 , A 1 ) ⇒ A 1 ): A 1
//简写形式:
def reduce(op:(A 1 , A 1 ) ⇒ A 1 )
```
#### 说明
| reduce方法 | API | 说明 |
| ---------- | ----------------- | ------------------------------------------------------------ |
| 泛型 | [A1 >: A] | (下界)A1必须是集合元素类型的父类, 或者和集合类型相同 |
| 参数 | op: (A1, A1) ⇒ A1 | 传入函数对象,用来不断进行聚合操作<br />第一个A1类型参数为:当前聚合后的变量<br />第二个A1类型参数为:当前要进行聚合的元素 |
| 返回值 | A1 | 列表最终聚合为一个元素 |
> 注意:
>
> reduce和reduceLeft效果一致,表示从左到右计算
>
> reduceRight表示从右到左计算
#### 需求
1. 定义一个列表,包含以下元素:1,2,3,4,5,6,7,8,9,10
2. 使用reduce计算所有元素的和
#### 参考代码
```scala
//案例: 演示聚合计算(reduce)
object ClassDemo32 {
def main(args: Array[String]): Unit = {
// 1. 定义一个列表,包含以下元素: 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10
val list1 = (1 to 10).toList
// 2. 使用reduce计算所有元素的和
//val list 2 = list 1 .reduce((x, y) => x + y)
//简写形式:
val list2 = list1.reduce(_ + _)
val list3 = list1.reduceLeft(_ + _)
val list4 = list1.reduceRight(_ + _)
println(s"list2 : $list2")
println(s"list3 : $list3")
println(s"list4 : $list4")
}
}
```
#### 7.8.2 折叠(fold)
fold与reduce很像,只不过多了一个指定初始值参数.
#### 格式
```scala
def fold[A 1 >: A](z: A 1 )(op:(A 1 , A 1 ) => A 1 ): A 1
//简写形式:
def fold(初始值)(op:(A 1 , A 1 ) => A 1 )
```
#### 说明
| fold方法 | API | 说明 |
| -------- | ---------------- | ------------------------------------------------------------ |
| 泛型 | [A1 >: A] | (下界)A1必须是集合元素类型的父类 |
| 参数 | z: A1 | 初始值 |
| 参数 2 | op: (A1, A1) ⇒ A | 传入函数对象,用来不断进行折叠操作<br />第一个A1类型参数为:当前折叠后的变<br />第二个A1类型参数为:当前要进行折叠的元 |
| 返回值 | A1 | 列表最终折叠为一个元素 |
> 注意事项:
>
> - fold和foldLet效果一致,表示从左往右计算
> - foldRight表示从右往左计算
#### 需求
1. 定义一个列表,包含以下元素:1,2,3,4,5,6,7,8,9,10
2. 假设初始化值是100, 使用fold方法计算所有元素的和.
#### 参考代码
```scala
//案例: 演示折叠计算(fold)
object ClassDemo33 {
def main(args: Array[String]): Unit = {
// 1. 定义一个列表,包含以下元素: 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10
val list1 = (1 to 10).toList
// 2. 假设初始化值是 100 , 使用fold计算所有元素的和
//val list 2 = list 1 .fold( 100 )((x, y) => x + y)
//简写形式:
val list2 = list1.fold(100)(_ + _)
val list3 = list1.foldLeft(100)(_ + _)
val list4 = list1.foldRight(100)(_ + _)
println(s"list 2 : $list2")
println(s"list 3 : $list3")
println(s"list 4 : $list4")
}
}