可变和不可变集合

根据容器中元素的组织方式和操作方式,可以分为有序和无序、可变和不可变等不同的容器类别

不可变集合是指集合内的元素一旦初始化完成就不可再进行更改,任何对集合的改变都将生成一个新的集合;

可变集合提供了改变集合内元素的方法;

Scala同时支持可变集合和不可变集合,主要下面两个包:

  • scala.collection.mutable:定义了可变集合的特质和具体实现类

  • scala.collection.immutable:定义了不可变集合的特质和具体实现类

对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本。

Scala优先采用不可变集合,不可变集合元素不可更改,可以安全的并发访问。

Scala集合有三大类:Seq(序列)、Set(集)、Map(映射);

所有的集合都扩展自Iterable特质。


immutable不可变集合:


mutable可变集合:


小结:

String属于IndexedSeq;

Queue队列和Stack堆这两个经典的数据结构属于LinearSeq;

Map体系下有一个SortedMap,说明Scala中的Map是可以支持排序的;

mutable可变集合中Seq中的Buffer下有ListBuffer,它相当于可变的List列表;

List列表属于Seq中的LinearSeq;

Seq

Seq代表按照一定顺序排列的元素序列;

该序列是一种特别的可迭代集合,包含可重复的元素;

元素的顺序是确定的,每个元素对应一个索引值;

Seq提供了两个重要的子特质:

  • IndexedSeq:提供了快速随机访问元素的功能,它通过索引来查找和定位的

  • LinearSeq:提供了访问head、tail的功能,它是线型的,有头部和尾部的概念,通过遍历来查找。

List

List代表元素顺序固定的不可变的链表,它是Seq的子类,在Scala编程中经常使用。

List是函数式编程语言中典型的数据结构,与数组类似,可索引、存放类型相同的元素。

List一旦被定义,其值就不能改变。

List列表有头部和尾部的概念,可以分别使用head和tail方法来获取:

  • head返回的是列表第一个元素的值

  • tail返回的是除第一个元素外的其它元素构成的新列表

这体现出列表具有递归的链表结构。

Scala定义了一个空列表对象Nil,定义为List[Nothing]

借助 Nil 可将多个元素用操作符 :: 添加到列表头部,常用来初始化列表;

操作符 ::: 用于拼接两个列表;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package lagou.cn.part09

object ListDemo {
def main(args: Array[String]): Unit = {
//Nil表示一个空的列表
//::操作符表示向集合中添加元素,它是从右往左进行运算的,所以集合对象一定要放在最右边
val list1 = 1 :: 2 :: 3 :: 4 :: Nil
val list2 = 5 :: 6 :: 7 :: 8 :: Nil

//使用:::操作符进行了拼接,不要使用::进行列表的拼接,因为这样拼接的结果不是我们想要的
val list3=list1:::list2

println(list3.head) //返回第一个元素
println(list3.tail) //返回除第一个元系之外的其他元素构成的新列表
println(list3.init) //返回除最后一个元素之外的其他元素构成的新列表
println(list3.last) //返回最后一个元素

val list4=List(4,2,6,1,7,9)
println(quickSort(list4))
}

def quickSort(list:List[Int]):List[Int]={
list match {
case Nil=>Nil
case head::tail=>
//通过partition将tail分为两部分
//小于head的元素放入less列表中,大于head的元素放入greater列表中
val (less,greater) =tail.partition(_<head)
quickSort(less):::head::quickSort(greater)
}
}
}

Queue

队列Queue是一个先进先出的结构。

队列是一个有序列表,在底层可以用数组或链表来实现。

先进先出的原则,就是先存入的数据,要先取出,后存入的数据后取出。

在Scala中,有scala.collection.mutable.Queue和scala.collection.immutable.Queue,一般来说,我们使用的是scala.collection.mutable.Queue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package lagou.cn.part09

import scala.collection.mutable

object QueueDemo {
def main(args: Array[String]): Unit = {
//创建一个可变的队列
val queue1=new mutable.Queue[Int]()
println(queue1)

//队列当中添加元素
queue1 +=1
//队列当中添加List列表
queue1 ++=List(2,3,4)
println(queue1)

//按照进入队列的顺序,删除队列当中的元素
//返回队列中的第一个元素,并从队列中删除这个元素
val dequeue=queue1.dequeue()
println(dequeue)
println(queue1)

//再向队列中添加元素
queue1.enqueue(5,6,7)
println(queue1)

//获取第一个、最后一个元素
println(queue1.head)
println(queue1.last)
}
}

Set

Set(集合)是没有重复元素的对象集合,Set中的元素是唯一的;

Set分为可变的和不可变的集合;

默认情况下,使用的是不可变集合(引用 scala.collection.immutable.Set);

使用可变集合,需要引用 scala.collection.mutable.Set 包;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package lagou.cn.part09

object SetDemo {
def main(args: Array[String]): Unit = {
//创建一个Set集合
val set = Set(1, 2, 3, 4, 5, 6)
// 删除元素
set.drop(1)
println(set)

//创建一个可变的Set
import scala.collection.mutable.Set
val mutableSet = Set(3, 4, 5)

//对可变的Set进行增加元素、删除元素的操作
mutableSet.add(7)
println(mutableSet)

mutableSet.remove(7)
println(mutableSet)

//通过使用+= -=进行增加、删除元素的操作
mutableSet += 8
mutableSet -= 3
println(mutableSet)

//对Set集合进行交集的操作(& intersect)
println(Set(1, 2, 3) & Set(2, 3, 4))
println(Set(1, 2, 3).intersect(Set(2, 3, 4)))

//对Set集合进行并集的操作(++ | union)
println(Set(1,2,3) ++ Set(2,3,4))
println(Set(1,2,3) | Set(2,3,4))
println(Set(1,2,3).union(Set(2,3,4)))

//对Set集合进行差集的操作(-- &~ diff)
println(Set(1,2,3) -- Set(2,3,4))
println(Set(1,2,3) &~ Set(2,3,4))
println(Set(1,2,3).diff(Set(2,3,4)) )
}
}

Map

Map(映射)是一系列键值对的容器;Scala 提供了可变的和不可变的两种版本的Map,分别定义在包 scala.collection.mutable 和 scala.collection.immutable 里;

默认情况下,Scala中使用不可变的 Map;

如果要使用可变Map,必须导入scala.collection.mutable.Map; 在Map中,键的值是唯一的,可以根据键来对值进行快速的检索。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package lagou.cn.part09
import scala.collection.mutable

object MapDemo {
def main(args: Array[String]): Unit = {
//使用两种方式定义Map
val map1 = Map("a" -> 1, "b" -> 2)
val map2 = Map(("a", 1), ("b", 2))

map1.keys.foreach(println(_))
map1.values.foreach(println(_))

//如果访问不存在的Key值时,会抛出异常
// println(map1("c"))

//也可以使用get方法,来获取与Key值相对应的Value值。
//get方法会返回一个Option对象,要么要是Some(有值),要么是None(无值)
val num: Option[Int] = map1.get("c")
num match {
case None => println("None")
case Some(x) => println(x)
}

//获取Key值所对应的Value值,如果键Key不存在,那么就返回指定的默认值
val num2: Int = map1.getOrElse("d", 0)
println(num2)

//创建一个可变的Map
val map3 = scala.collection.mutable.Map("a" -> 1, "b" -> 2)
println(map3)
map3("a") = 10
println(map3)

//增加一个元素
map3("c") = 3
println(map3)

//通过+=添加元素,-=删除元素
map3 += ("d" -> 4, "f" -> 5)
println(map3)
map3 -= "d"
println(map3)

//将Key与Value的值互换
val kv: mutable.Map[Int, String] = for ((k, v) <- map3) yield (v, k)
println(kv)
//推荐使用下面的方式将Key与value的值互换
map3.map(x=>(x._2,x._1)).foreach(println(_))

//通过拉链操作创建Map
val a=Array(1,2,3)
val b=Array("a","b","c")
val c: Array[(Int, String)] = a.zip(b)
val d: Map[Int, String] = a.zip(b).toMap
println(d)
}
}

集合常用算子

map、foreach & mapValues

集合对象都有 foreach、map 算子。

两个算子的共同点在于:都是用于遍历集合对象,并对每一项执行指定的方法;

两个算子的差异点在于:

foreach无返回值(准确说返回void),用于遍历集合

map返回集合对象,用于将一个集合转换成另一个集合

1
2
3
4
5
6
7
8
9
// 使用 foreach 打印集合元素
val numlist = (1 to 10).toList
numlist.foreach(elem=>print(elem+" "))
numlist.foreach(print _)
numlist.foreach(print)

// 使用 map 对集合进行转换
numlist.map(_ > 2)
numlist.map(_ * 2)

操作 Map集合时,mapValues用于遍历value,是map操作的一种简化形式;

1
2
3
4
5
6
7
8
9
10
// Range(20, 0, -2)用给定的步长值设定一个范围,从开始到结束(不包含)。
//Map(20 -> 0,18 -> 1,16 -> 2,14 -> 3,12 -> 4,10 -> 5,8 -> 6,6 -> 7,4 -> 8,2 -> 9)
val map = Range(20, 0, -2).zipWithIndex.toMap

// 将map集合中的value值+100
map.map(elem => (elem._1, elem._2 + 100))
map.map{case (k,v) => (k, v+100)}

// mapValues的表达最简洁
map.mapValues(_+100)

flatten & flatMap

flatten的作用是把嵌套的结构展开,把结果放到一个集合中;

在 flatMap 中传入一个函数,该函数对每个输入都返回一个集合(而不是一个元素),最后把生成的多个集合“拍扁”成为一个集合;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
scala> val lst1 = List(List(1,2), List(3,4))
lst1: List[List[Int]] = List(List(1, 2), List(3, 4))

scala> lst1.flatten
res5: List[Int] = List(1, 2, 3, 4)

// flatten 把一个字符串的集合展开为一个字符集合,因为字符串本身就是字符的集合
scala> val lst4 = List("Java", "hadoop")
lst4: List[String] = List(Java, hadoop)

scala> lst4.flatten
res8: List[Char] = List(J, a, v, a, h, a, d, o, o, p)

// flatten 有效的处理 Some 和 None 组成的集合。它可以展开Some元素形成一个新的集合,同时去掉 None元素
scala> val x = Array(Some(1), None, Some(3), None)
x: Array[Option[Int]] = Array(Some(1), None, Some(3), None)

// 方法很多,flatten最简单
scala> x.flatten
res9: Array[Int] = Array(1, 3)

scala> x.collect{case Some(i) => i}
res10: Array[Int] = Array(1, 3)

scala> x.filter(!_.isEmpty).map(_.get)
res11: Array[Int] = Array(1, 3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 下面两条语句等价
val lst = List(List(1,2,5,6),List(3,4))

// 将 lst 中每个元素乘2,最后作为一个集合返回
// 此时 flatMap = flatten + map
//List(1,2,5,6,3,4)
lst.flatten.map(_*2)
lst.flatMap((x: List[Int]) => x.map(_*2))
lst.flatMap(_.map(_*2))

// 将字符串数组按空格切分,转换为单词数组
val lines = Array("Apache Spark has an advanced DAG execution engine", "Spark offers over 80 high-level operators")

// 下面两条语句效果等价
//map算子产生的结果:Array(Array(Apache, Spark, has, an, advanced, DAG, execution, engine), Array(Spark, offers, over, 80, high-level, operators))
// flatten算子产生的结果:Array(Apache, Spark, has, an, advanced, DAG, execution, engine, Spark, offers, over, 80, high-level, operators)
lines.map(_.split(" ")).flatten
// 此时 flatMap = map + flatten
lines.flatMap(_.split(" "))

备注:flatMap = flatten + map 或 flatMap = map + flatten

collect

collect通过执行一个并行计算(偏函数),得到一个新的数组对象

1
2
3
4
5
6
7
8
9
10
11
12
13
object CollectDemo {
//通过下面的偏函数,把chars数组的小写a转换为大写的A
val fun: PartialFunction[Char, Char] = {
case 'a' => 'A'
case x => x
}

def main(args: Array[String]): Unit = {
val chars = Array('a', 'b', 'c')
val newchars = chars.collect(fun)
println("newchars:" + newchars.mkString(","))
}
}

reduce

reduce可以对集合当中的元素进行归约操作;

还有 reduceLeft 和 reduceRight ,reduceLeft 从左向右归约,reduceRight 从右向左归约;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
val lst1 = (1 to 10).toList
lst1.reduce(_+_)

// 为什么这里能出现两个占位符?
lst1.reduce(_+_)

// 我们说过一个占位符代表一个参数,那么两个占位符就代表两个参数。根据这个思路改写等价的语句
// x类似于buffer,缓存每次操作的数据;y每次操作传递新的集合元素
lst1.reduce((x, y) => x + y)

// 利用reduce操作,查找 lst1 中的最大值
lst1.reduce((x,y) => if (x>y) x else y)

// reduceLeft、reduceRight
lst1.reduceLeft((x,y) => if (x>y) x else y)
lst1.reduceRight((x,y) => if (x>y) x else y)

sorted sortwith & sortby

Scala中对于集合的排序有三种方法:sorted、sortBy、sortWith

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
object SortDemo {
def main(args: Array[String]): Unit = {
val list = List(1, 9, 3, 8, 5, 6)

//sorted方法对一个集合进行自然排序
//sorted源码:def sorted[B >: A](implicit ord: Ordering[B]): Repr
//源码中有两点值得注意的地方:
// 1.sorted方法中有个隐式参数ord: Ordering。
// 2.sorted方法真正排序的逻辑是调用的java.util.Arrays.sort
val numSort: List[Int] = list.sorted
println(numSort)

//sortBy源码:def sortBy[B](f: A => B)(implicit ord: Ordering[B]): Repr = sorted(ord on f)
//sortBy最后调用的sorted方法
println(list.sortBy(x => x).reverse)

//sortWith源码:def sortWith(lt: (A, A) => Boolean): Repr = sorted(Ordering fromLessThan lt)
print(list.sortWith(_ > _))
}
}

与Java集合的转换

使用 scala.collection.JavaConverters 与Java集合交互。它有一系列的隐式转换,添加了asJava和asScala的转换方法。

1
2
3
4
5
import scala.collection.JavaConverters._

val list: Java.util.List[Int] = List(1,2,3,4).asJava

val buffer: scala.collection.mutable.Buffer[Int] = list.asScala