简介
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
中的元素没有外部引用,则会自动删除。