① Scala同时支持不可变集合和可变集合,不可变集合可以安全的并发访问
② Scala集合中两个主要的包: 可变集合: scala.collection.mutable 不可变集合:scala.collection.immutable
③ Scala默认采用不可变集合,对于几乎所有的集合类,Scala都同时提供了可变(mutable)和不可变(immutable)的版本
④ Scala的集合有三大类:序列Seq、集Set、映射Map,所有的集合都扩展自Iterable特质,在Scala中集合有可变(mutable)和不可变(immutable)两种类型
可变集合和不可变集合举例
① 可变集合:可变集合,就是这个集合本身可以动态变化的,比如:ArrayList , 是可以动态增长的。scala.collection.mutable 包中的集合类声明后则是可变的,这里的可变指的是原集合本身的可变,所以处理可变集合意味着你需要去理解哪些代码的修改会导致集合同时改变。
② 不可变集合:Scala不可变集合,就是这个集合本身不能动态变化,类似java的数组,是不可以动态增长或缩减的。scala.collection.immutable 包中的集合类声明后是不可变的,这个不可变对引用类型而言就是指的引用地址不可变,例如一个集合创建之后将不会改变,可以对该集合实现添加、移除或修改等操作,但是该操作不是修改了原集合的值,而是返回了一个新的集合将原集合的值经过操作后的值放入新集合返回给你而已,原集合值还是没有改变。
不可变集合继承层次一览
不可变集合小结:
① Set、Map是Java中也有的集合,而Seq是Java没有的,我们发现List归属到Seq了,因此Scala中的List就和java中的List不是同一个概念了
② 我们前面的for循环有一个 1 to 3,就是IndexedSeq 下的Vector,String也是属于IndexeSeq
③ 我们发现经典的数据结构,比如Queue 和 Stack被归属到LinearSeq
④ 大家注意Scala中的Map体系有一个SortedMap,说明Scala的Map也可以支持排序
⑤ IndexSeq 和 LinearSeq 的区别,IndexSeq是通过索引来查找和定位,因此速度快,比如String就是一个索引集合,通过索引即可定位,而LineaSeq 是线型的,即有头尾的概念,这种数据结构一般是通过遍历来查找,它的价值在于应用到一些具体的应用场景 (电商网站,大数据推荐系统 :最近浏览的10个商品)
Scala可变集合继承关系一览图
1.1 数组-定长不可变数组(声明泛型)
在Scala中,Array代表的含义与Java中类似,也是长度不可改变的数组,但内容可以修改。此外,由于Scala与Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上是Java数组。例如字符串数组在底层就是Java的String[],整数数组在底层就是Java的Int[]。
1) 声明方式一:
//声明一个长度为5,泛型为Int类型的数组,这种方式必须指定具体的泛型类型 val arr = new Array[Int](5)2)声明方式二:
val arr = Array(1,2,"a",true)通过Array(数组元素)方式声明数组,相当于调用了Array类的apply方法new了一个数组,然后对其赋值返回,这种方式可以不用指定泛型,可以添加不同类型的元素,因为这种方式默认是调用Array类中的apply方法,这个apply方法有很多指定好的参数具体数据类型的重载方法,例如:def apply(x: Int, xs: Int*): Array[Int],但其中有一个def apply[T: ClassTag](xs: T*): Array[T]={}方法,该方法没有指定泛型的具体类型,所以使用这种方式定义数组时,可以不指定具体的泛型类型,默认会重载调用该方法。
3)Scala不可变数组的常用操作
/** * @author huleikai * @create 2019-05-22 13:12 */ object TestArray { def main(args: Array[String]): Unit = { val arr = Array(1,2,"a",true) //访问数组中的某个元素,可以通过下标访问,需要注意的是将java中的[]换成了() println(arr(0)) // 1 //数组的遍历 for (i <- arr){ println("当前元素是:"+i) } //二维数组定义 val twoarr = Array(Array(1,2),Array("a","b")) //数组中经常使用的yield关键字,该作用是拿出数组中的每个元素然后经过处理返回一个新的数组 val res = for (e <- 1 until 10 if e % 2 == 0) yield e * 10 println(res.toBuffer) //ArrayBuffer(20, 40, 60, 80) //filter是过滤,接收一个返回值为 boolean 的函数,map 相当于将数组中的每一个元素取出来,经过map(f:A=>B)中参数传入的函数处理后返回一个新的结果数组,该map方法的参数只接收一个函数,该函数只有一个参数并且必须有返回值 val r = Array(1, 2, 3, 4, 5, 6, 7, 8, 9).filter(_ % 2 == 0).map(_ * 10) println(r.toBuffer) //ArrayBuffer(20, 40, 60, 80) //数组常用方法测试(这些方法大多在其他的集合类中也有),先定义一个数组 val arr1 = Array(1, 2, 3, 4, 5, 6, 7, 8, 9) println("arr1该数组中的最大值:"+arr1.max) //arr1该数组中的最大值:9 println("arr1该数组中的最小值:"+arr1.min) //arr1该数组中的最小值:1 println("arr1该数组中的头元素:"+arr1.head) //arr1该数组中的头元素:1 println("arr1该数组中的头元素:"+arr1.tail) //arr1该数组中的尾元素:2,3,4,5,6,7,8,9 //获取该数组的尾部(除head外)元素,返回一个新数组 val tail = arr1.tail println("arr1该数组中的尾元素:"+tail.toBuffer)//arr1该数组中的尾元素:ArrayBuffer(2, 3, 4, 5, 6, 7, 8, 9) //获取该数组的除last(最后一个元素)外的所有元素init val init = arr1.init println("arr1该数组中的init(除最后一个元素外)所有元素:"+arr1.toBuffer) //ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9) println("arr1该数组中的最后一个元素:"+arr1.last) //arr1该数组中的最后一个元素:9 //drop方法参数为删除的元素的个数,删除都是从第一个开始删除指定个数元素,然后将删除后的结果返回一个新数组 val arr2 = arr1.drop(5) println(arr2.toBuffer) //ArrayBuffer(6, 7, 8, 9) //使用mkString方法,参数为数组中元素的分隔符,将数组转化为字符串 val arrstr = arr1.mkString(",") println(arrstr) //1,2,3,4,5,6,7,8,9 //数组中的apply方法,参数传入数组对应的下标,返回数组中对应下标的值 val apply = arr1.apply(2) println(apply) //3 } }1.2 数组-变长数组(声明泛型)
1)声明方式
val arrbuf = new ArrayBuffer[String](10)变长数组ArrayBuffer,在使用时需要import scala.collection.mutable.ArrayBuffer包,这里的10是可以自己指定的初始容量,也可以不指定使用默认值。当然也可以使用ArrayBuffer(元素...)的方式,即使用apply方式声明,如下所示:
val arrbuf = ArrayBuffer(1,2,"a",true)2)可变数组ArrayBuffer的CURD操作
/** * @author huleikai * @create 2019-05-22 13:41 */ object TestArrayBuffer { def main(args: Array[String]): Unit = { val arrbuf = new ArrayBuffer[String](10) //追加元素 arrbuf.append("a") println(arrbuf) //ArrayBuffer(a) //修改元素 arrbuf.update(0,"b") println(arrbuf) //ArrayBuffer(b) //查询某个元素 println(arrbuf(0)) //b //删除元素 arrbuf.remove(0) println(arrbuf) //ArrayBuffer() } }3)可变数组ArrayBuffer的其他操作
import scala.collection.mutable.ArrayBuffer /** * @author huleikai * @create 2019-05-22 13:41 */ object TestArrayBuffer { def main(args: Array[String]): Unit = { val arrbuf = new ArrayBuffer[String](10) //向ArrayBuffer的尾部追加单个元素使用 += arrbuf += "a" //追加多个元素 arrbuf += ("b","c") println(arrbuf) //ArrayBuffer(a, b, c) //向尾部追加一个数组 ++= arrbuf ++= Array("d","e") println(arrbuf) //ArrayBuffer(a, b, c, d, e) //向ArrayBuffer的尾部追加一个ArrayBuffer arrbuf ++= ArrayBuffer("f","g") println(arrbuf) //ArrayBuffer(a, b, c, d, e, f, g) //在ArrayBuffer数组某个位置插入元素用 def insert(n: Int, elems: A*),A*表示动态可变参数 arrbuf.insert(0,"k","m") println(arrbuf) //ArrayBuffer(k, m, a, b, c, d, e, f, g) //使用ArrayBuffer中的flatmap方法 val buffer: ArrayBuffer[String] = arrbuf.flatMap(_.split(",")) println(buffer) //ArrayBuffer(k, m, a, b, c, d, e, f, g) } }4)变长数组小结
① ArrayBuffer是变长数组,类似java的ArrayList
② val arr2 = ArrayBuffer[Int]() 也是使用的apply方法构建对象
③ def append(elems: A*) { appendAll(elems) } 接收的是可变参数.
④ 每append一次,arr在底层会重新分配空间,进行扩容,arr的内存地址会发生变化,但并不是返回一个新的数组,最终新开辟的空间的引用和原数组是同一个引用
5)定长数组与变长数组的转换
注意:不管是定长数组转化为可变长数组,还是可变长数组转化为定长数组,这里的arrbuf和arr仍然保持原来的属性不变,变化了的只是调用转化方法的返回结果对象!
1.3 多维数组的定义和使用
/** * @author huleikai * @create 2019-05-22 13:41 */ object TestArrayBuffer { def main(args: Array[String]): Unit = { //下面的声明表示生成一个3行4列的二维数组 val arr = Array.ofDim[Double](3,4) //循环遍历二维数组 for(i <- arr){ for(j <- i){ println("二维数组中的每个元素: "+j) } } } }1.4 数组-Scala数组与Java的List的互转
① Scala数组转Java的List
import scala.collection.mutable.ArrayBuffer import scala.collection.JavaConversions.bufferAsJavaList /** * @author huleikai * @create 2019-05-22 13:41 */ object TestArrayBuffer { def main(args: Array[String]): Unit = { // Scala集合和Java集合互相转换 val arr = ArrayBuffer("1", "2", "3") /** * public ProcessBuilder(List<String> command) { * if (command == null) * throw new NullPointerException(); * this.command = command; * } */ val javaArr = new ProcessBuilder(arr) val arrList = javaArr.command() println(arrList) //输出 [1, 2, 3] } }② Java的List转Scala数组(mutable.Buffer)
import scala.collection.mutable.ArrayBuffer /** * @author huleikai * @create 2019-05-22 13:41 */ object TestArrayBuffer { def main(args: Array[String]): Unit = { // Scala数组转Java的List val arr = ArrayBuffer("1", "2", "3") import scala.collection.JavaConversions.bufferAsJavaList val javaArr = new ProcessBuilder(arr) val arrList = javaArr.command() println(arrList) //输出 [1, 2, 3] //Java的List转Scala数组(mutable.Buffer) import scala.collection.JavaConversions.asScalaBuffer import scala.collection.mutable val scalaArr: mutable.Buffer[String] = arrList scalaArr.append("jack") println(scalaArr) //ArrayBuffer(1, 2, 3, jack) } }2.1 元组的概念
元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。说的简单点,就是将多个无关的数据封装为一个整体,称为元组,最大的特点灵活,对数据没有过多的约束,但需要注意的是:同一元组中最大只能有22个元素,如需扩展可以使用元组的嵌套。
2.2 元组的创建
/** * @author huleikai * @create 2019-05-22 15:10 */ object TestTuple { def main(args: Array[String]): Unit = { //元组的创建 val tuple = (1,2,"a",false,'a',0.01) println(tuple) //(1,2,a,false,a,0.01) } }2.3 元组的访问
访问元组中的数据,可以采用顺序号(_顺序号),也可以通过索引(productElement)访问。
2.4 元组Tuple数据的遍历
注意:Tuple是一个整体,遍历需要调其迭代器。
2.5 元组的拉链操作
/** * @author huleikai * @create 2019-05-22 15:10 */ object TestTuple { def main(args: Array[String]): Unit = { //元组的拉链操作 val arr1:Array[Int] = Array(88,99,22) val arr2:Array[String] = Array("a","b","c") val mapzip: Array[(String, Int)] = arr2.zip(arr1) println(mapzip.toBuffer) //ArrayBuffer((a,88), (b,99), (c,22)) //因为数组中每个元组的元素内容对称,可以使用toMap转化成Map集合 val map = mapzip.toMap println(map) //Map(a -> 88, b -> 99, c -> 22) } }Scala中的 List 和Java中的 List 不一样,在Java中List是一个接口,真正存放数据是实现类ArrayList,而Scala的List可以直接存放数据,就是一个object,默认情况下Scala的List是不可变的,List属于序列Seq。
3.1 Scala不可变列表List
1)列表List的创建
//声明一个不可变的List列表 val list = List(1,2,3) //空集合 val emptyList = Nil2)列表List元素的访问
//声明一个不可变的List列表 val list = List(1,2,3) //访问List列表中的元素 val firstElement: Int = list(0) println(firstElement) //获取第一个元素,下标从0开始3)列表List常见操作
因为List是类似于数组的结构,所以向列表中增加元素,会返回新的列表集合对象,原来的列表对象的内容并没有变化。不可变的列表List是位于 import scala.collection.immutable._ ,在 Scala 中列表要么为空(Nil 表示空列表),要么是一个 head 元素加上一个 tail 列表。:: 操作符是将给定的头和尾创建一个新的列表,例如 9 :: 2,表示9是head而2是tail,返回的新的列表 List(9,2) ,需要注意的是 :: 操作符是右结合的,如 9 :: 5 :: 2 :: Nil 相当于 9 :: (5 :: (2 :: Nil)),即它是从右向左开始将::操作符两边的head和tail结合的,最后返回的就是List(9,5,2)。
/** * @author huleikai * @create 2019-05-22 15:45 */ object TestList { def main(args: Array[String]): Unit = { //声明一个不可变的List列表 val list = List(1,2,3) //从右向左开始将::操作符两边的head和tail结合 val lst1 = 0::list println(lst1) //List(0, 1, 2, 3) //::本身就是一个方法,该方法将传入的参数插入到指定List的头部 val lst2 = list.::(0) println(lst2) //List(0, 1, 2, 3) val lst3 = lst1 :: list println(lst3) //List(List(0, 1, 2, 3), 1, 2, 3) //+:方法跟::作用一样,都是将元素插入到List的head位置 val lst4 = 0 +:list println(lst4) //List(0, 1, 2, 3) val lst5 = list.+:(0) println(lst5) //List(0, 1, 2, 3) val list0 = List(4,5) //将 2 个 list 合并成一个新的 List val lst6 = list ++ list0 println(lst6) //List(1, 2, 3, 4, 5) //将list0插入到list后面生成一个新的集合,list .++:(list0)跟这个结果相反4,5,1,2,3 val lst7 = list ++:list0 println(lst7) //List( 1, 2, 3,4, 5) //:::方法和++:方法功能一样,如果是list0:::list的话就是4,5,1,2,3 val lst8 = list0.:::(list) println(lst8) //List(1, 2, 3, 4, 5) //将一个元素添加到list的后面产生一个新的集合 val lst9 = list:+ 4 println(lst9) //List(1, 2, 3, 4) //采用::及 Nil 进行列表构建 val num1 = 1 :: (2 :: (3 :: (4 :: Nil))) println(num1) //List(1, 2, 3, 4) //由于::操作符的优先级是从右往左的,因此上一条语句等同于下面这条语句 val num2=1::2::3::4::Nil println(num2) //List(1, 2, 3, 4) //判断是否为空 val boo = num1.isEmpty println(boo) //false //取第一个元素 val head = num1.head println(head) //1 //取除第一个元素外剩余的元素,返回的是列表 val tail = num1.tail println(tail.toBuffer) //ArrayBuffer(2, 3, 4) //取列表第二个元素 val secord = num1.tail.head println(secord) //2 //List 连接操作 val mergearr = List(1,2,3):::List(4,5,6) println(mergearr) //List(1, 2, 3, 4, 5, 6) //取除最后一个元素外的元素,返回的是列表 val init = num1.init println(init) //List(1, 2, 3) //取列表最后一个元素 val last = num1.last println(last) //4 //丢弃前 n 个元素 val drop = mergearr.drop(3) println(drop) //List(4, 5, 6) //获取前 n 个元素 val take = list.take(2) println(take) //List(1, 2) //将列表进行分割 val spilt = list.splitAt(1) println(spilt) //(List(1),List(2, 3)) //Zip 操作 val chars=List('1','2','3','4') val zip = list.zip(chars) println(zip) // List[(Int, Char)] =List((1,1), (2,2), (3,3)) //List mkString 方法 val mk = list.mkString("-") println(mk) //1-2-3 //转换成数组 val array = list.toArray println(array.toBuffer) //ArrayBuffer(1, 2, 3) //将list转化为String类型 val string = list.toString() println(string) // String = List(1, 2, 3) } }3.2 Scala可变列表 ListBuffer
ListBuffer是可变的list集合,可以添加,删除元素,ListBuffer属于序列Seq。
//构建一个可变列表,初始有 3 个元素 1,2,3 val listbuf1 = ListBuffer[Int](1,2,3) println(listbuf1) //创建一个空的可变列表 val listbuf2 = new ListBuffer[Int] println(listbuf2) //ListBuffer() //向listbuf2中追加元素,注意:没有生成新的集合 listbuf2 +=4 println(listbuf2) //ListBuffer(4) //使用ListBuffer的append()方法向集合中追加新的元素 listbuf2.append(5) println(listbuf2) //ListBuffer(4, 5) //将listbuf2中的元素追加到listbuf1中,注意:没有生成新的集合 listbuf1 ++= listbuf2 println(listbuf1) //ListBuffer(1, 2, 3, 4, 5) //将listbuf1和listbuf2合并成一个新的 ListBuffer 注意:生成了一个集合 val newlistbuf = listbuf1 ++ listbuf2 println(newlistbuf) //ListBuffer(1, 2, 3, 4, 5, 4, 5) //将元素追加到 listbuf2 的后面生成一个新的集合 val lst = listbuf2 :+ 5 println(lst) //ListBuffer(4, 5, 5) //将0插入到ListBuffer的最前面,生成一个新集合,原集合不变 val aaa = 0 +:listbuf1 println(aaa) //ListBuffer(0, 1, 2, 3, 4, 5) println(listbuf1) //ListBuffer(1, 2, 3, 4, 5) //将0插入到ListBuffer的最后面生成一个新的集合,原集合不变 val bbb = listbuf1 :+ 0 println(bbb) //ListBuffer(1, 2, 3, 4, 5, 0) println(listbuf1) //ListBuffer(1, 2, 3, 4, 5)队列是一个有序列表,在底层可以用数组或是链表来实现,遵循先入先出的原则。在Scala中,有 scala.collection.mutable.Queue 和 scala.collection.immutable.Queue,一般来说,我们在开发中通常使用可变集合中的队列。
1)队列的创建
2)队列单个元素及List追加
val queue = new mutable.Queue[Int] println(queue) //队列单个元素的追加 queue +=12 //队列追加一个List queue ++=List(1,2,3)3)队列删除和加入元素
4)返回队列的元素
//返回队列的第一个元素 println(queue.head) //1 //返回队列最后一个元素 println(queue.last) //5 //返回队列的尾部,即返回除了第一个以外剩余的元素,可以级联使用 println(queue.tail) //Queue(2, 3, 4, 5) println(queue.tail.tail.tail) //Queue(4, 5)Scala中的Map和Java类似,也是一个散列表,它存储的内容也是键值对(key-value)映射,Scala中不可变的Map是有序的,可变的Map是无序的。Scala中,有可变Map (scala.collection.mutable.Map) 和不可变Map(scala.collection.immutable.Map)。
5.1 构造不可变映射
Scala中的不可变Map是有序的,构建Map中的元素底层是Tuple2类型。
案例小结:
① 从输出的结果看到,输出顺序和声明顺序一致
② 构建Map集合中,集合中的元素其实是Tuple2类型
③ 默认情况下(即没有引入其它包的情况下),Map是不可变map
5.2 构造可变映射
案例小结:从输出的结果看到,可变映射的输出顺序和声明顺序不一致,即它是无序的!
5.3 映射Map的其他声明方式
//创建一个空的可变映射Map val mutableMap = new scala.collection.mutable.HashMap[String, Int] //创建一个空的不可变映射Map val immutableMap: Map[String, Int] with Object = new HashMap[String,Int]() //对偶方式直接创建Map val map = Map(("A",1),("B",2),("C",3))5.4 映射Map的取值方式
如果我们上面案例中的取值方式,则存在一些风险,如果key不存在,则抛出异常 java.util.NoSuchElementException,在Java中,如果key不存在则返回null,所以在Scala中有Option、None、Some这三个类型,None、Some 是 Option 的子类,它主要解决值为 null 的问题,在 java 语言中, 对于定义好的 HashMap,如果 get 方法中传入的键不存在,方法会返回 null,在编写代码的时候对于 null 的这种情况通常需要特殊处理,然而在实际中经常会忘记,因此它很容易引起 NullPointerException 异常。在 Scala 语言中通过 Option、None、Some 这三个类来避免这样的问题,这样做有几个好处,首先是代码可读性更强,当看到 Option 时,我们自然而然就知道它的值是可选的,Scala中的Map底层在调用map.get(key)时,会将结果进行一次包装,key存在则包装成Some类型返回,不存在则返回None,当我们需要拿到具体值的时候,我们可以再调用map.get(key).get获取。
5.5 映射Map的增加、修改、删除
① 不可变映射Map的增删改
注意:不可变指的是原映射Map本身的不可变,而上面的增删改每个操作不是修改原映射Map本身,而是完成增删改之后新生成的一个映射Map!
② 可变映射Map的增删改
注意:从上面的结果,我们可以看出,可变映射Map进行增删改操作后,其改变的是原映射Map本身的值!
5.6 映射 Map 的遍历
注意:可变映射与不可变映射的遍历方式都是一样的,通过上面的遍历我们也发现了,可变映射是有序的,而不可变映射是无序的!
集Set是不重复元素的集合。集不保留顺序,默认是以哈希集实现。默认情况下,Scala 使用的是不可变集合,如果你想使用可变集合,需要引用 scala.collection.mutable.Set 包。
6.1 Set集合的创建
6.2 Set可变集合的元素添加和删除
6.3 集合Set的遍历
6.4 集合Set的更多操作
序号
方法
描述
1
def +(elem: A): Set[A]
为集合添加新元素,并创建一个新的集合,除非元素已存在
2
def -(elem: A): Set[A]
移除集合中的元素,并创建一个新的集合
3
def contains(elem: A): Boolean
如果元素在集合中存在,返回 true,否则返回 false。
4
def &(that: Set[A]): Set[A]
返回两个集合的交集
5
def &~(that: Set[A]): Set[A]
返回两个集合的差集
6
def ++(elems: A): Set[A]
合并两个集合
7
def drop(n: Int): Set[A]]
返回丢弃前n个元素新集合
8
def dropRight(n: Int): Set[A]
返回丢弃最后n个元素新集合
9
def dropWhile(p: (A) => Boolean): Set[A]
从左向右丢弃元素,直到条件p不成立
10
def max: A
查找最大元素
11
def min: A
查找最小元素
12
def take(n: Int): Set[A]
返回前 n 个元素