简介
scala集合分为可变和不可变两种,默认都使用不可变集合,即集合元素是在构造时候确定的,而且构造完成之后里面的内容就是不可变的了,每次对集合的操作都会产生一个新的集合。使用不可变集合可能在有些场景下回损失性能,但是这么做是并发安全的。
可变集合需要单独引入scala.collection.mutable包,每次是在原来集合的基础上执行操作的。
scala也有线性集合、平衡数结构和哈希结构等,这些也都有可变和不可变版本。
先看下不可变集合的关系图:
再看下可变集合关系图:
看下图例说明:
Trait Iterable
Iterable是一个trait结构,我们简单说下该结构定义的一些通用接口。
合并或新增:一般借助concat,又可以写为++,合并两个集合,或者追加另一个Iterable的所有元素到当前Iterable中
Map: 元素映射操作,对Iterable中的元素执行某种规则的映射,映射后的元素会放入新的集合中
转换操作: 比如toList,toVector这种
还有其他一系列操作,这里不再赘述,参考:docs.scala-lang.org/overviews/c…
序列Trait
这里总共有三种,分别是:Seq、 IndexedSeq、 和LinearSeq
这些Trait有长度,而且可以利用索引获取数据。
具体操作可以参考:docs.scala-lang.org/overviews/c…
我们单独说一下追加元素的操作,假设序列是xs,需要追加的元素是x,以下所说的追加需要根据可变和不可变具体区分:
x +: xs // 头部追加x
ys ++: xs // ys中所有的元素追加到xs的前面
xs :+ x // 尾部追加x
xs :++ ys // ys中所有的元素追加到xs后面
Sets
包含所有不重复元素的集合。具体看docs.scala-lang.org/overviews/c…
注意下SortedSets有序集合,默认使用二叉树实现,具体可以TreeSet来实例化。
默认使用哈希前缀树实现
Map
k-v映射关系的结构,具体参考文档:docs.scala-lang.org/overviews/c…
注意下getOrElse方法即可。
默认使用哈希前缀树实现
不可变集合说明
Lists
不可变的序列,有以下几个常数时间操作:
- 获取头部元素和剩下的
Lists - 向头部追加元素
其余的操作基本都是线性时间。
LazyLists
类似Lists的结构,唯一的区别在于所有的元素都是都是延迟计算的。正因为这一点,该集合可以”无限长“。
我们使用#::向头部追加元素。
举个代码实例:
package example
object MyExample {
def main(args: Array[String]): Unit = {
val lazyList = 1 #:: 2 #:: 3 #:: LazyList.empty
println(s"list: ${lazyList.toString}, length: ${lazyList.length}")
println("========")
println(lazyList.take(lazyList.length).toString)
println("========")
println(lazyList.take(lazyList.length).toList.toString)
}
}
代码输出:
list: LazyList(<not computed>), length: 3
========
LazyList(<not computed>)
========
List(1, 2, 3)
只有调用toList的时候才会执行真正的计算,否则该List没有实际的元素。
我们给出一个斐波那契数列的例子来进一步说明:
package example
object MyExample {
def fibForm(a: Int, b: Int): LazyList[Int] = a #:: fibForm(b, a + b)
def main(args: Array[String]): Unit = {
val fibs = fibForm(1, 1).take(7) // 这是只是定义了形式
val res = fibs.toList // 这里执行真正的计算
println(res.toString) // 输出计算的结果
}
}
LazyLists可以用无限递归的方式表示有限的集合,这是其最大的用处。
ArraySeqs
前面提到的Lists无法直接索引数据,因此我们引入ArraySeqs结构来根据下标索引数据。利用下标获取所有的元素都是常数时间。任何更改操作的复杂度都是,因为涉及到复制原来所有的元素
注意一点,所有的更改操作不会更改集合本身,而是产生一个新的集合:
package example
import scala.collection.immutable.ArraySeq
object MyExample {
def main(args: Array[String]): Unit = {
val arr = ArraySeq(1, 2, 3)
val arr1 = arr.updated(1, 5)
println(arr.toString())
println(arr1.toString())
}
}
代码输出:
ArraySeq(1, 2, 3)
ArraySeq(1, 5, 3)
Vectors
前面提到的Lists和ArraySeq都有级别的操作。Vectors对上面两者的常数的操作类型取并集,比如头部追加元素产生新Vectors的时间也是常数级别的,可以按照下标随机索引元素等;但是花费的常数时间相对会多一些。
Vectors内部使用的是因子树结构,我们只需要知道它不是一个线性结构即可,具体可以参考这篇文档
如果我们想要ArraySeq和Lists常数时间并集的操作,再考虑Vectors结构
Queue
快速的FIFO结构,注意dequeue的方法返回的是出队元素和剩余的队列。
Ranges
相等间隔的数据,给出实例代码:
package example
object MyExample {
def main(args: Array[String]): Unit = {
val rng = Range(1, 2, 3)
println(rng.toString())
}
}
代码输出:
inexact Range 1 until 2 by 3
scala的for循环本质上也是在Range迭代,比如:
package example
object MyExample {
def main(args: Array[String]): Unit = {
val rng = 1 to 3
println(rng)
}
}
代码输出:
Range 1 to 3
既然如此,我们可以使用Range实现间隔迭代,比如:
package example
object MyExample {
def main(args: Array[String]): Unit = {
for (i <- Range(0, 10, 2)) {
println(i)
}
}
}
代码输出:
0
2
4
6
8
红黑树
TreeSet 和 TreeMap 结构是红黑树,我们需要范围索引时,可以使用这种结构。
VectorMaps 和 ListMaps
两者表示,k-v索引时,命中相同key时,values存放的结构。
可变集合说明
ArrayBuffer
该结构适合构建大的总是新增元素的集合。
几个优势:
- 常数时间下标随机索引
- 快速在尾部追加元素
ListBuffer
链表结构,除非我们经常调用toList方法,否则还是使用ArrayBuffer比较好
StringBuilder
可以在原有的String上新增元素,而不是每次都新建String。
+=:追加新的字符++:追加String
ArrayDeque
如果想快速在在数组头部追加元素,选择该结构;否则使用前面提到的ArrayBuffer
Queue
队列操作,不支持随机索引,但是FIFO操作最快
Stack
栈操作,FILO最快
ArraySeq
该结构有固定的size,同时满足以下几个场景再使用该结构:
size固定- 非常快速操作
HashSet & HashMap
可变的哈希表
WeakHashMap
比较适合cache功能,如果map中的元素没有外部引用,则会自动删除。