Scala的集合
一、集合概述
1)Scala 的集合有三大类:序列 Seq、集 Set、映射 Map,所有的集合都扩展自 Iterable特质。
2)对于几乎所有的集合类,Scala 都同时提供了可变和不可变的版本,分别位于以下两个包
不可变集合:scala.collection.immutable
可变集合: scala.collection.mutable
3)Scala 不可变集合,就是指该集合对象不可修改,每次修改就会返回一个新对象,而不会对原对象进行修改。类似于 java 中的 String 对象
4)可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象。类似于 java 中 StringBuilder 对象
建议:在操作集合的时候,不可变用符号,可变用方法
不可变集合继承图:
1)Set、Map 是 Java 中也有的集合
2)Seq 是 Java 没有的,我们发现 List 归属到 Seq 了,因此这里的 List 就和 Java 不是同一个概念
3) for 循环中的 1 to 3,就是 IndexedSeq 下的 Range
4)String 也是属于 IndexedSeq
5)我们发现经典的数据结构比如 Queue 和 Stack 被归属到 LinearSeq(线性序列)
6)大家注意 Scala 中的 Map 体系有一个 SortedMap,说明 Scala 的 Map可以支持排序
7)IndexedSeq 和 LinearSeq 的区别:
(1)IndexedSeq是通过索引来查找和定位,因此速度快,比如 String 就是一个索引集合,通过索引即可定位
(2)LinearSeq是线型的,即有头尾的概念,这种数据结构一般是通过遍历来查找
可变集合继承图:
二、数组
2.1 不可变数组
1)第一种方式定义数组
定义:val arr1:Array = new Array[Int] (10)
(1)new 是关键字
(2)[Int]是指定可以存放的数据类型,如果希望存放任意数据类型,则指定 Any
(3)(10),表示数组的大小,确定后就不可以变化
object TestArray{
def main(args: Array[String]): Unit = {
#(1)数组定义
val arr01 = new Array[Int](4)# 声明了长度,数据默认是0
println(arr01.length) // 4
#(2)数组赋值
//(2.1)修改某个元素的值
arr01(3) = 10
//(2.2)采用方法的形式给数组赋值
arr01.update(0,1)
#(3)遍历数组
//(3.1)查看数组
println(arr01.mkString(","))//使用,对元素进行连接
//(3.2)普通for循环遍历
for(i<- 0 until arr01.length){
println(arr01(i))
}
//增强for循环遍历
for(i<-arr01.indices) println(arr01(i))
for(elem<-arr01) println(arr01(i))
//(3.3)foreach()方法遍历
arr01.foreach((x)=>{println(x)})
arr01.foreach(println)
#(4)增加元素(由于创建的是不可变数组,增加元素,其实是产生新的数组)
val newArr1 = arr01.:+(73) // 在数组尾部增加一个元素
val newArr2 = arr01.+:(30) // 在数组头部增加一个元素
#Scala 提供了更简便的操作 -_-#
val new Arr3 = arr01 :+ 15
val new Arr4 = 19 +: 29 +: arr01 :+ 25
#个人建议,将 : 比作数组本身
}
}
2)第二种方式定义数组
val arr1 = Array(1, 2)
(1)在定义数组时,直接赋初始值
(2)使用 apply 方法创建数组对象
object TestArray{
def main(args: Array[String]): Unit = {
var arr02 = Array(1, 3, "bobo")
println(arr02.length)
for (i <- arr02) {
println(i)
}
}
}
当使用 new 关键字构建对象时,调用的其实是类的构造方法,当直接使用类名构建对象时,调用的其实是伴生对象的 apply 方法。
使用arr(i)访问数组元素实际上是使用了伴生对象的apply方法
不可变数组指的是数组的引用地址,数组被声明后,其地址大小不能改变,但是能改变其中的数值。
2.2 可变数组
1)定义变长数组
val arr01 = ArrayBuffer[Any](3, 2, 5)
(1)[Any]存放任意数据类型
(2)(3, 2, 5)初始化好的三个元素
(3)ArrayBuffer 需要引入 scala.collection.mutable.ArrayBuffer
(4)如果没有指定初始化元素ArrayBuffer默认长度16
import scala.collection.mutable.ArrayBuffer
object TestArrayBuffer {
def main(args: Array[String]): Unit = {
#(1)创建并初始赋值可变数组
val arr01 = ArrayBuffer[Any](1, 2, 3)
// val arr01 = ArrayBuffer()
# (2) 遍历方式和不可变数组一样
#(3)增加元素
//可变数组不能够使用 :+ 的方式进行添加,该方式会产生新数组,适用于不可变数组
//(3.1)追加数据
arr01 += 4 //arr01 = arr01 + 4 向数组尾部添加
5 +=: arr01 //向数组头部添加
//(3.2)向数组最后追加数据
arr01.append(5,6)
//(3.3)向指定的位置插入数据
arr01.insert(0,7,8) //下标为0的位置插入
arr01.insertAll(下标,数组) //添加一个数组
// (3.4) 向数组头部添加数据
arr01.prepend(11,76)
#(4)修改元素
arr01(1) = 9 //修改第 2 个元素的值
arr01.remove(下标)
arr01.remove(起始下标,终止下标) //删除一段元素
arr01 -= 3 #通过数值进行删除,如果元素值不存在则不进行更改
}
推荐使用不可变数组,防止有多个对象指向同一个数组地址。
2.3 不可变数组与可变数组的转换
arr1.toBuffer //不可变数组转可变数组
arr2.toArray //可变数组转不可变数组
(1)arr2.toArray 返回结果才是一个不可变数组,arr2 本身没有变化
(2)arr1.toBuffer 返回结果才是一个可变数组,arr1 本身没有变化
可变数组转不可变数组
val arr:ArrayBuffer[Int] = ArrayBuffer(23,56,98)
val newArr:Array[Int] = arr.toArray
不可变数组转可变数组
val Buffer:mutable.Buffer[Int] = newArr.toBuffer
2.4 多维数组
多维数组定义:
val arr = Array.ofDim[Double] (3,4)
说明:
1.二维数组中有三个一维数组,每个一维数组中有四个元素
2.最多支持5维数组
object DimArray {
def main(args: Array[String]): Unit = {
//(1)创建了一个二维数组, 有三个元素,每个元素是,含有 4 个元素一维数组()
val arr = Array.ofDim[Int](3, 4)
arr(1)(2) = 88
//(2)遍历二维数组
for( i<-arr.indices;j <- arr(i).indices){
println(array(i)(j))
}
array.foreach(line => line.foreach(println))
}
}
三、列表List
3.1 不可变List
object TestList {
def main(args: Array[String]): Unit = {
//(1)List 默认为不可变集合
//(2)创建一个 List(数据有顺序,可重复)
val list: List[Int] = List(1,2,3,4,3)
//(7)空集合 Nil
val list5 = 1::2::3::4::Nil
//(4)List 增加数据
//(4.1)::的运算规则从右向左
//val list1 = 5::list
val list1 = 7::6::5::list
//(4.2)添加到第一个元素位置
val list2 = list.+:(5)
//(5)集合间合并:将一个整体拆成一个一个的个体,称为扁平化
val list3 = List(8,9)
//val list4 = list3::list1
val list4 = list3:::list1
//(6)取指定数据
println(list(0))
//(3)遍历 List
list5.foreach(println)
}
}
Nil是一个空的集合
:: 是一个方法,能将左边元素添加到右边列表
::: 能够合并列表,将列表拆分后在相加可以使用++代替
3.2 可变List
import scala.collection.mutable.ListBuffer
object TestList {
def main(args: Array[String]): Unit = {
//(1)创建一个可变集合
val buffer = ListBuffer(1,2,3,4)
//(2)向集合中添加数据
buffer.+=(5)
buffer.append(6)
buffer.insert(1,2)
//(3)打印集合数据
buffer.foreach(println)
//(4)修改数据
buffer(1) = 6
buffer.update(1,7)
//(5)删除数据
buffer.-(5)
buffer.-=(5)
buffer.remove(5)
// (6)合并集合
list1 ++=: list2 //改变的是list2
lsit3 = list1 ++ list2 //合并两个集合
}
}
四、Set集合
默认情况下,Scala 使用的是不可变集合,如果你想使用可变集合,需要引用scala.collection.mutable.Set 包
4.1 不可变Set
(1)Set 默认是不可变集合,数据无序
(2)数据不可重复
object TestSet {
def main(args: Array[String]): Unit = {
//(1)Set 默认是不可变集合,数据无序
val set = Set(1,2,3,4,5,6)
//(2)数据不可重复
val set1 = Set(1,2,3,4,5,6,3) #数据无序且不重复
//(3)添加元素
val set2 = set1.+ 20 //set1 + 20
//(4)合并set
val set3 = set1 ++ set2
//(5)删除元素
val set4 = set3 - 20
}
}
Scala中通过包名区分不可变set和可变set
4.2 可变 mutable.Set
object TestSet {
def main(args: Array[String]): Unit = {
//(1)创建可变集合
//set是Scala中的一种特质,需要使用伴生对象创建
val set = mutable.Set(1,2,3,4,5,6)
//(3)集合添加元素
set += 8 //底层还是调用的add
set.add(8)//返回值是Boolean
//(4)向集合中添加元素,返回一个新的 Set
val set2 = set.+(9)
println("set2=" + set)
//(5)删除数据
set-=(5)
set.remove(3)
//(2)打印集合
set.foreach(println)
println(set.mkString(","))
//(6)合并集合
val set4 = set ++ set2
set1 ++= set2
}
}
五、Map集合
Scala 中的 Map 和 Java 类似,也是一个散列表,它存储的内容也是键值对(key-value)映射
5.1 不可变Map
//创建不可变map
val map1:Map[String,Int]=Map("a"->13,"b"->25,"hello"->3)
println(map1) //Map(a -> 13, b -> 25, hello -> 3)
//遍历元素
map1.foreach(println) //(a,13) (b,25) (hello,3)
//map1.foreach((kv:(String,Int))=>println((kv)))
//取出map中的key或value
for(key<-map1.keys){ //a-->Some(13) b-->Some(25) hello-->Some(3)
println(s"$key-->${map1.get(key)}")
}
//为了防止空指针异常,将value包装成了一个option类型,有数据时为some,空时为None
//访问value
println(map1.get("a").get) //13
println(map1.get("aimyon"))//None
//这种获取方式是不安全的,因为在编译时不知道get是否能获取到值
//当一个key的vlaue为None时,使用get会出异常
println(map1.getOrElse("aimyon","0")) //为None时为0
println(map1("a")) //13
5.2 可变Map
//创建可变map
val map2: mutable.Map[String, Int] = mutable.Map("a" -> 13, "b" -> 25, "hello" -> 3)
println(map2) //Map(a -> 13, b -> 25, hello -> 3)
println(map2.getClass) //class.scala.collection.mutable.HashMap
//添加元素
map2.put("aimyon", 36)
map2 += (("e", 6))
println(map2) //Map(e -> 6, b -> 25, a -> 13, aimyon -> 36, hello -> 3)
//删除元素
map2.remove("e")
println(map2.getOrElse("e", 0)) //0
map2 -= "b"
println(map2) //Map(a -> 13, aimyon -> 36, hello -> 3)
//修改元素
map2.update("c", 5)
map2.update("a", 1)
println(map2) //Map(a -> 1, c -> 5, aimyon -> 36, hello -> 3)
//合并map
val map3: Map[String, Int] = Map("aaa" -> 13, "bbb" -> 25, "hello world" -> 3)
map2 ++= map3 //map3是不可变的,map2为可变的 只能够将合并后的给map2
print(map2)
//Map(bbb -> 25, aaa -> 13, a -> 1, hello world -> 3, c -> 5, aimyon -> 36, hello> 3)
//合并后,会将相同的key覆盖
六、元组
元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。说的简单点,就是将多个无关的数据封装为一个整体,称为元组。
注意:元组中最大只能有 22 个元素。
//创建元组
val tuple = ("hello",122,'a',true)
println(tuple)
//访问数据
println(tuple._1) //hello
println(tuple._3) // a
println(tuple.productElement(2))// 122
//遍历元组
for (elem <- tuple.productIterator)
println(elem)
//嵌套元组
val mulTuple = (12,0.3,"aimyon",(23,"scala"),29)
println(mulTuple._4._2) //scala
七、集合的常用操作和基本属性
val list = List(1,3,4,5,6,36)
val set = Set(23,34,51,66)
//获取集合长度 or 大小
println(list.length) //只有线性序列(LinearSeq)才能获取length
println(set.size) //所有集合类型都能使用size
//循环遍历
for(elem<-list)
println(elem)
//迭代器
for(elem<-list.iterator)
println(elem)
//生成字符串
println(list.mkString("--")) //1--3--4--5--6--36
//是否包含某个元素
println(list.contains(23)) //false
println(set.contains(23)) //true
7.1 衍生集合的操作
衍生集合:一个集合经过一些操作后返回的新的集合
单集合操作:
val list1 = List(1,3,5,7,2,89)
val list2 = List(3,56,213,44,1,36)
//获取集合的头 通常使用在有效的集合
println(list1.head) //1
//获取集合的尾 值的是除了head之外的元素
println(list1.tail) //List(3, 5, 7, 2, 89)
//获取最后一个元素
println(list1.last) //89
//获取集合初始数据
println(list1.init) //List(1, 3, 5, 7, 2)
//反转集合
println(list1.reverse) //List(89, 2, 7, 5, 3, 1)
//取出前(后)n个元素
println(list1.take(3)) //前3个 List(1, 3, 5)
println(list1.takeRight(4))//从右开始4个 List(5, 7, 2, 89)
//去掉前(后)n个元素
println(list1.drop(3)) //List(7, 2, 89)
两个集合操作:
val list1 = List(1,3,5,7,2,89)
val list2 = List(3,56,213,44,1,36)
//并集 如果是set集合做并集操作(set1 ++ set2),元素将会去重,且无序
val union = list1.union(list2)
println(union)
println(list1 ::: list2)
//List(1, 3, 5, 7, 2, 89, 3, 56, 213, 44, 1, 36)
//交集
val intersection = list1.intersect(list2)
println(intersection)
//List(1,3)
//差集,某个集合独有的元素
val diff1 = list1.diff(list2)
val diff2 = list2.diff(list1)
println(diff1) //List(5, 7, 2, 89)
println(diff2) //List(56, 213, 44, 36)
//拉链,将左右两边的元素一一对应组合成二元组,多的将会被去除
println("zip1:"+ list1.zip(list2))
print("zip2:"+ list2.zip(list1))
//zip1:List((1,3), (3,56), (5,213), (7,44), (2,1), (89,36))
//zip2:List((3,1), (56,3), (213,5), (44,7), (1,2), (36,89))
//滑窗 sliding(size,step)
for (elem<-list1.sliding(3)){
println(elem)
}
//List(3, 5, 7)
//List(5, 7, 2)
//List(7, 2, 89)
7.2 集合计算简单函数
val list = List(5,1,82,-1,4)
val list2 = List(("a",5),("b",3),("c",7),("d",-3),("e",22))
//求和
println(list.sum) //91
//求乘积
println(list.product) //-1640
//最大值
println(list.max) //82
println(list2.max) //默认比较第一个元素,"e"的ASC码最大
println(list2.maxBy((tuple:(String,Int))=>tuple._2))//传入一个方法取出要比较的元素
//最小值
println(list.min) // -1
//从大到小逆序排序
println(list.sorted.reverse)\
//List(82, 5, 4, 1, -1)
//传入隐式参数
println(list.sorted(Ordering[Int].reverse))
//List(82, 5, 4, 1, -1)
//根据规则排序
println(list.sortWith((a:Int,b:Int) => {a<b})) //从小到大排序
//List(-1, 1, 4, 5, 82)
7.3 集合计算高级函数
(1)过滤
遍历一个集合并从中获取满足指定条件的元素组成一个新的集合
(2)转化/映射(map)
将集合中的每一个元素映射到某一个函数
(3)扁平化
拆分元素,再合并成一个集合
(4)扁平化+映射
注:flatMap 相当于先进行 map 操作,在进行 flatten 操作集合中的每个元素的子元素映射到某个函数并返回新集合
(5)分组(group)
按照指定的规则对集合的元素进行分组
(6)简化(归约)
(7)折叠
Map类操作
val list = List(1,2,3,4,5,6,7,8,9,10)
//过滤 偶数
val evenList = list.filter(_%2==0) //(elem:Int) => {elem%2==0}
println(evenList) //List(2, 4, 6, 8, 10)
//map映射
println(list.map(_*2)) //List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
println(list.map(elem=>elem*elem)) //List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)
//扁平化
val nestedList:List[List[Int]] = List(List(1,2,3),List(6,5),List(6,7,8,9))
val flatList = nestedList(0) ::: nestedList(1) ::: nestedList(2)
val flatList2 = nestedList.flatten
println(flatList2) //List(1, 2, 3, 6, 5, 6, 7, 8, 9)
//扁平映射
//将一组字符串进行分词,并保存成单词的列表
val strings:List[String] = List("hello world","aimyon","ado")
val splitList:List[Array[String]] = strings.map(_.split(" "))
println(splitList)
//List([Ljava.lang.String;@58ceff1, [Ljava.lang.String;@7c30a502[Ljava.lang.String;@49e4cb85)
val flattenList = splitList.flatten
println(flattenList) //List(hello, world, aimyon, ado)
val flatmapList = strings.flatMap(_.split(" "))
println(flatmapList) //List(hello, world, aimyon, ado)
//分组group by
val groupMap = list.groupBy(_%2)
val groupMap2 = list.groupBy(data =>{
if(data % 2 == 0) "偶数" else "奇数"
})
println(groupMap)
//Map(1 -> List(1, 3, 5, 7, 9), 0 -> List(2, 4, 6, 8, 10))
println(groupMap2)
//Map(奇数 -> List(1, 3, 5, 7, 9), 偶数 -> List(2, 4, 6, 8, 10))
Reduce类操作:
val list = List(1,2,3,4,5,6,7,8,9,10)
//从左到右两两归约 结果和sun相同
println(list.reduce(_+_))
println(list.reduceLeft(_+_))
println(list.reduceRight(_+_))
//上边结果都是55
val list2 = List(3,4,5,8,10)
println(list2.reduce(_-_)) //-24
println(list2.reduceLeft(_-_)) //-24
println(list2.reduceRight(_-_)) //6 3 -(4 - (5 - (8 -10)))
Fold折叠类操作:
val list = List(1,2,3,4,5,6,7,8,9,10)
//fold
println(list.fold(10)(_+_)) //10+1+2+3+4+5... 65
println(list.foldLeft(10)(_-_))// 10-1-2-3-4... -45
println(list.foldRight(11)(_-_))//... 3-(4 - (5- (6-(7-...(10-11))))) 6
应用案例:
我们前边使用的Map合并,发现map1++map2中,如果存在相同的key值,map2会覆盖掉map1的value;
我们希望这种操作能做到相同的累加,不相同的添加。
val map1 = Map("a"->1,"b"->3,"c"->6)
val map2 = mutable.Map("a"->6,"b"->2,"c"->8,"d"->3)
println(map1++map2) //Map(a -> 6, b -> 2, c -> 8, d -> 3)
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) //Map(b -> 5, d -> 3, a -> 7, c -> 14)
WordCount案例:
val list:List[String] = List("hello world","hello java","hello scala","hello flick from scala","hello")
val wordList = list.flatMap(_.split(" "))
val wordMap = wordList.groupBy(word => word)
val groupWord = wordMap.map(kv => (kv._1,kv._2.length))
val sortList = groupWord.toList.sortWith(_._2 >_._2)
println(sortList)
//List((hello,5), (scala,2), (world,1), (java,1), (from,1), (flick,1))
八、队列和并行集合
队列:
Scala 也提供了队列(Queue)的数据结构,队列的特点就是先进先出。进队和出队的方法分别为 enqueue 和 dequeue。
object TestQueue {
def main(args: Array[String]): Unit = {
val que = new mutable.Queue[String]()
que.enqueue("a", "b", "c")
println(que.dequeue())//a
println(que.dequeue())//b
println(que.dequeue())//c
//不可变队列
val queue2 = Queue("A","V","C")
val queue3 = queue2.enqueue("d")
println(queue3)
}
}
并行集合:
Scala 为了充分使用多核 CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算
object TestPar {
def main(args: Array[String]): Unit = {
val result1 = (0 to 100).map{case =>
Thread.currentThread.getName}
//Vector(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1)
val result2 = (0 to 100).par.map{case =>
Thread.currentThread.getName}
//ParVector(12, 12, 12, 18, 12, 12, 12, 12, 12,13,19,16)
}
}