本文最后更新于:2023-04-29T14:46:28+08:00
集合总览
简介
在Scala中,集合主要分为三大类型:序列(Seq)、集合(Set)以及映射(Map),并且所有的集合都扩展自Iterable特征。
对于几乎所有集合类,Scala中都同时提供了可变 和不可变 版本,区分主要是按照集合的位置来区分:
不可变集合:scala.collection.immutable
可变集合:scala.collection.mutable
可变与不可变指的是引用是否可变,类比Java中的String和StringBuilder。String是不可变的,在对String进行修改的时候,实际上是返回了一个新的对象;StringBuilder是可变的,对它进行修改的时候是在对原对象进行修改。
对于不可变集合,集合的长度数量不可修改,每次修改(比如增删元素)都会返回一个新的对象,而不会修改源对象。
可变集合可以对源对象任意修改,一般也提供不可变集合相同的返回新对象的方法,但也可以用其他方法来修改源对象。
一般情况下一个集合类的可变和不可变版本的名称不同,但是有时候也可能相同,这时候就需要按照集合所属的包来进行区分。后面我们会介绍一些典型集合的操作方式,一般建议是:在操作集合的时候,不可变集合使用符号操作,可变集合使用方法操作
不可变集合总览
Set和Map是Java中也有的集合,对应的特点也和Java中相同
在Scala的Map体系中有一个SortedMap,支持排序
Seq是Java中没有的特征,其中分为随机访问序列(IndexedSeq)和线性序列(LinearSeq)
IndexedSeq通过索引来查找和定位,因此速度快
LinearSeq是线性的,有头尾的概念,这种数据结构一般通过遍历来查找(像这里队列,栈等被归类到了LinearSeq,因为我们一般只关注它们的首尾元素,操作的也是首尾元素)
前面我们学习的for语法,底层对应的就是IndexedSeq下的Range
图中的Array和String使用虚线连接,它们其实对应的就是Java中的数组和java.lang.String
,和Scala中的集合并没有直接关系。但是通过可以通过隐式转换成为一个包装类型之后就可以当作集合了
Scala中更多地推荐使用不可变集合,能使用不可变就使用不可变
可变集合总览
可变集合相较于不可变集合中多出了Buffer特征,其余的整体结构相差不大
数组
不可变数组 Array
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 val arr1 = new Array [Int ](5 )val arr2 = Array (5 , 4 , 3 , 2 , 1 )for (i <- 0 until arr1.length) arr1(i) = i arr2.update(0 , 6 )for (i <- 0 until arr1.length) print(arr1(i) + " " ) println()for (elem <- arr1) print(elem + " " ) println()val iterator = arr2.iteratorwhile (iterator.hasNext) { print(iterator.next() + " " ) } println() arr2.foreach((elem: Int ) => { print(s"${elem} " ) }) println() println(arr2.mkString(" " ))val newArr1 = arr1 :+ 10 val newArr2 = arr1.+:(20 ) val newArr3 = 30 +: arr2 val newArr4 = 20 +: 10 +: arr2 :+ 30 :+ 40 println(newArr1.mkString(" " )) println(newArr2.mkString(" " )) println(newArr3.mkString(" " )) println(newArr4.mkString(" " ))
不可变数组在创建的时候有两种方式
方式1:指定大小,不指定初值
方式2:直接指定初值
不可变数组的值赋值和修改是不会返回新对象的,但是增删元素是会返回新对象的
需要注意这里添加符号的使用:+
和+:
,:
始终朝向对象
遍历方式对于集合类型来说基本上都是通用的
下标越界会抛出异常,在使用之前应该注意
建议不可变集合使用符号来操作
可变数组 ArrayBuffer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 val arr1 = new ArrayBuffer [Int ]()val arr2 = ArrayBuffer (5 , 4 , 3 , 1 , 1 ) arr1 += 10 arr2.append(30 ) arr2.insert(0 , 8 , 9 ) val arr3 = arr1 :+ 22 val arr4 = 33 +: arr1 arr1.remove(0 ) arr2 -= 1
可变数组和不可变数组之间可以相互转化,转化不会修改本身的类型,而是返回一个结果
Array -> ArrayBuffer:toBuffer()
ArrayBuffer -> Array:toArray()
多维数组 Array.ofDim
多维数组可以使用Array.ofDim[Type]来指定维度
1 2 3 4 5 6 7 8 9 10 11 val array = Array .ofDim[Int ](2 , 3 ) array(0 )(2 ) = 10 array(1 )(0 ) = 25 array.foreach(line =>{ line.foreach(print) })
Scala中ofDim
方法最多支持到构造5维的数组,更多维度的数组不支持
列表
不可变列表 List
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 val list1 = List (92 , 39 , 52 , 23 ) println(list1(1 )) list1.foreach(elem => print(s"${elem} " )) println()val list2 = 10 +: list1 val list3 = list1 :+ 10 println(list1) println(list2) println(list3)val list4 = list1.::(49 ) val list5 = 49 :: list1 println(list4) println(list5)val list6 = 12 :: 123 :: 23 :: 98 :: Nil println(list6)val list7 = list1 :: list6 val list8 = list1 :::list6val list9 = list1 ++ list6 println(list7) println(list8) println(list9)
List是抽象类,本身不能直接new,需要调用伴生对象的apply创建对象
支持+:
和:+
向首尾添加元素
Nil
表示空列表,::
添加元素到表头
合并列表使用:::
或者++
List本身可以使用apply进行随机访问,但是不能进行update更改,即不能通过类似list1(0) = 1
的方式进行修改
可变列表 ListBuffer
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 val list1 = new ListBuffer [Int ]()val list2 = ListBuffer (12 , 23 , 44 , 53 ) list1.append(32 ) list2.prepend(46 ) list1.insert(1 , 12 , 32 ) 10 +=: 11 +=: list1 += 12 += 13 println(list1) println(list2) list1(1 ) = 11 list2.update(1 , 11 ) println(list1) println(list2) list1.remove(0 ) list2 -= 46 println(list1) println(list2)val list4 = list1 ++ list2 println(list4) println(list1) println(list2) list1 ++= list2 println(list1) println(list2)
集合
在Scala中,集合的可变和不可变版本的名称都是Set。默认情况下,Scala使用的是不可变集合,如果想要使用可变集合,则需要引用scala.collection.mutable.Set
,建议在使用的时候,不可变集合使用Set
,可变集合使用mutable.Set
不可变集合 Set
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 val set1 = Set (12 ,23 ,21 ,12 ,123 ,43 ,53 ,4 )val set2 = set1 + 111 println(set1) println(set2)val set3 = set1 - 123 println(set1) println(set3)val set4 = set1 ++ set2 println(set4)
数据无序且不可重复
添加元素:+
删除元素:-
合并:++
操作返回一个新的集合,不会修改源集合
可变集合 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 val set1 = mutable.Set (12 , 23 , 12 , 23 , 34 , 54 , 65 , 23 ) set1 += 11 val flag1 = set1.add(33 )val flag2 = set1.add(33 ) println(flag1) println(flag2) println(set1) set1 -= 11 val flag3 = set1.remove(12 )val flag4 = set1.remove(12 ) println(flag3) println(flag4) println(set1)val set2 = mutable.Set (12 , 5 , 23 , 1 , 4 ) set1 ++= set2 println(set1)
操作在源集合上
添加元素:+ add
删除元素:- remove
方法调用会返回操作是否成功的布尔值
合并操作:++
映射
Scala中的Map和Java中的类似,存储的内容是键值对。与Set相同,Map的可变与不可变类型的名称相同,只是存在不同的包中。默认的Map是不可变类型的,可变类型建议使用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 val map1: Map [String , Int ] = Map ("a" -> 1 , "b" -> 2 , "c" -> 3 ) println(map1) map1.foreach(println) map1.foreach((kv: (String , Int )) => println(kv))for (key <- map1.keys) println(s"${key} -> ${map1.get(key)} " )for (value <-map1.values) println(s"${value} " ) println(map1.get("a" )) println(map1.get("a" ).get) println(map1.get("d" ).get) println(map1.getOrElse("d" , 0 )) println(map1("c" )) val map2 = map1 ++ Map ("d" ->4 ) println(map2)
通过get(key)
得到的是一个Some对象,如果没有找到则返回None
Some对象通过get可以得到确切的值
可变映射 mutable.Map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 val map1:mutable.Map [String , Int ] = mutable.Map ("a" ->1 , "b" ->2 , "c" ->3 ) println(map1) map1.put("d" , 4 ) map1 += (("e" , 5 )) println(map1) map1.remove("c" ) map1 -= "d" println(map1) map1.put("a" ,-1 ) map1.update("b" , -2 ) println(map1)val map2:mutable.Map [String , Int ] = mutable.Map ("a" ->11 , "b" ->22 , "c" ->33 ) map1 ++= map2 println(map1)
元组
元组可以理解为是一个容器,其中可以存放相同和不同类型的数据。在Scala中,元组最多只能有22个元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 val tuple:(String , Int , Boolean ) = ("xxx" , 11 , true ) println(tuple) println(tuple._1) println(tuple._2) println(tuple._3) println(tuple.productElement(0 ))for (elem <- tuple.productIterator) println(elem)
Map中存放的就是二元元组,->
可以用来构建二元组
元组可以嵌套定义
队列
队列也分为可变和不可变,名称相同均为Queue
,同样是存在的包不同。默认为不可变队列,可变队列为mutable.Queue
1 2 3 4 5 6 7 8 val que = new mutable.Queue [String ]() que.enqueue("a" , "b" , "c" ) println(que.dequeue())
并行集合
Scala为了充分使用多核CPU,提供了并行集合,用于多核环境的并行计算。使用并行集合之后,在执行的时候会调用多个线程来加速执行。只需要在使用的集合类后添加.par
方法即可。并行集合依赖于scala.collection.parallel.immutable/mutable
,2.13版本之后不再通过标准库提供,需要单独下载。
1 2 3 4 val result1 = (0 to 100 ).map(_ => Thread .currentThread.getName)val result2 = (0 to 100 ).par.map(_ => Thread .currentThread.getName) println(result1) println(result2)
集合常用函数
基本属性和常用操作
length
:获取集合长度(线性序列才有长度)
size
:获取集合大小(所有集合类型都有大小)
遍历方式:下标遍历、元素遍历、迭代器遍历、foreach
iterator
:迭代器
mkString
:生成字符串
contains
:是否包含
衍生集合
衍生集合方法指的是调用操作之后得到的结果可能是一个集合
获取集合的头元素head
(元素)和剩下的尾tail
(除去第一个元素构成的集合)。
集合最后一个元素last
(元素)和除去最后一个元素的初始数据init
(集合)。
反转reverse
。
取前后n个元素take(n) takeRight(n)
去掉前后n个元素drop(n) dropRight(n)
交集intersect
并集union
,线性序列的话已废弃用concat
连接。
差集diff
,得到属于自己、不属于传入参数的部分。
拉链zip
,得到两个集合对应位置元素组合起来构成二元组的集合,大小不匹配会丢掉其中一个集合不匹配的多余部分。(类似Python的zip)
滑窗sliding(n, step = 1)
,框住特定个数元素,方便移动和操作。得到迭代器,可以用来遍历,每个迭代的元素都是一个n个元素集合。步长大于1的话最后一个窗口元素数量可能个数会少一些。
集合计算简单函数
求和sum
求乘积product
最小值min
最大值max
maxBy(func)
支持传入一个函数获取元素并返回比较依据的值
举例来说,元组默认就只会判断第一个元素,如果需要根据第二个元素判断,则返回第二个元素就行xxx.maxBy(_._2)
。
排序sorted
,默认从小到大排序。从大到小排序sorted(Ordering[Int].reverse)
。
按元素排序sortBy(func)
,指定要用来做排序的字段。也可以再传一个隐式参数逆序sortBy(func)(Ordering[Int].reverse)
自定义比较器sortWith(cmp)
,比如按元素升序排列sortWith((a, b) => a < b)
或者sortWith(_ < _)
,按元组元素第二个元素升序sortWith(_._2 > _._2)
。
集合计算高级函数
filter:遍历一个集合,并从中获取符合条件的元素组成一个新的集合
map:将集合中的每个元素通过某种映射关系得到另一个元素
flatten:将元素打散扁平化
flatMap:相当于先对每个集合进行map操作,然后再进行flatten操作
group:按照指定的规则对集合进行分组
reduce:依次将集合中的元素进行规约,最终得到一个输出
fold:与reduce类似,但是具有初始值
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 val list = List (1 , 2 , 3 , 4 )val evenList = list.filter((elem: Int ) => { elem % 2 == 0 }) println(evenList) println(list.filter(_ % 2 == 1 )) println(list.map(_ * 2 )) println(list.map(x => x * x))val nestedList: List [List [Int ]] = List (List (1 , 2 , 3 ), List (4 , 5 , 6 ), List (7 , 8 , 9 )) println(nestedList.flatten)val strings: List [String ] = List ("Hello World" , "Hello Java" , "Hello Scala" ) println(strings.flatMap(_.split(" " )))val groupMap: Map [Int , List [Int ]] = list.groupBy(_ % 2 ) println(groupMap) println(list.reduce(_ + _)) println(list.reduceLeft(_ + _)) println(list.reduceRight(_ + _)) println(list.reduce(_ - _)) println(list.reduceLeft(_ - _)) println(list.reduceRight(_ - _)) println(list.fold(10 )(_ + _)) println(list.foldLeft(10 )(_ - _)) println(list.foldRight(10 )(_ - _))
注意foldRight和reduceRight的顺序
fold提供的初始值必须是同一类型,foldLeft中可以传入不同类型的初始值
集合应用案例
定制Map合并
Map的默认合并操作是使用后面的相同Key中的value进行覆盖,现在需要定制一个合并方式,逻辑为相同的Key进行value的叠加
(注意这里foldLeft
的选用逻辑)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 val map1 = Map ("a" -> 1 , "b" -> 3 , "c" -> 4 )val map2 = mutable.Map ("a" -> 6 , "b" -> 1 , "c" -> 9 , "d" -> 10 )val map3 = map1.foldLeft(map2)( (mergedMap, kv) => { val key = kv._1 val value = kv._2 mergedMap(key) = mergedMap.getOrElse(key, 0 ) + value mergedMap } ) println(map3)
简单Word Count
对字符串进行切分,之后统计每个词出现的次数,并取其中排名前三的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 val stringList = List ("hello" , "hello world" , "hello scala" , "hello spark from scala" , "hello flink form scala" )val res = stringList .flatMap(_.split(" " )) .groupBy(word => word) .map((kv: (String , List [String ])) => (kv._1, kv._2.length)) .toList .sortWith(_._2 > _._2) .take(3 ) println(res)
进阶Word Count
原始输入中以及提供了部分出现次数的信息,利用这些信息,同样是完成上面Word
Count的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 val tupleList: List [(String , Int )] = List ( ("hello" , 1 ), ("hello world" , 2 ), ("hello scala" , 3 ), ("hello spark from scala" , 1 ), ("hello flink from scala" , 2 ) )val res = tupleList .flatMap((content: (String , Int )) => { val strings = content._1.split(" " ) strings.map(word => (word, content._2)) }) .groupBy(content => content._1) .map(content => { val word = content._1 val countList = content._2 val sum = countList.map(_._2).sum (word, sum) }).toList.sortWith(_._2 > _._2).take(3 ) println(res)
Scala与Java中的容器转换
在Scala中我们经常会调用Java中的方法,但是这些方法可能返回的是Java中的容器对象。Scala中的容器与Java中的容器是可以相互转换的,这些转换可以通过一些方法很容易的完成。不过首先需要我们引入对应包:
1 import scala.collection.JavaConverters ._
之后可以调用对应的asScala
和asJava
方法,Java中的容器对象可以使用asScala
方法,而Scala中的容器对象可以使用asJava
方法。之后调用对应的toxxx
方法就可以完成。举例来说,我现在有一个Java中的List数组,我想把它转化为Scala中的Array,则需要通过如下方式进行:
1 2 3 4 5 6 import scala.collection.JavaConverters ._val listInJava: java.util.List [String ] = new ArrayList (); val arrayInScala = listInJava.asScala.toArray;
当然一些容器之间可以直接相互转换,而另一些容器之间则只支持单向的转换。转换关系如下,其中左边的是Scala中的容器对象,右边则是Java中的容器对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Iterator <=> java.util.Iterator Iterator <=> java.util.Enumeration Iterable <=> java.lang.Iterable Iterable <=> java.util.Collection mutable.Buffer <=> java.util.List mutable.Set <=> java.util.Set mutable.Map <=> java.util.Map mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap Seq => java.util.List mutable.Seq => java.util.List Set => java.util.Set Map => java.util.Map
参考文章
scala-lang-docs
Java中的List转化为Scala中的Array