这里有两个问题:一个是关于列表的非常基本的问题,另一个是 scala 特定的。首先,我会画一张图。 “List”抽象数据类型是计算机科学中最古老的数据类型之一;它可以追溯到 1956 年,也是 LISP 语言的第一个实现。
列表 ADT 确实是使用单向链表实现的。每个元素传统上称为一个 cons 单元格(Nil 表示列表结尾):
+------+------+ +------+------+ +--------+
| 1 | * -+----> | 3 + * -+--->| NIL |
+------+------+ +-------------+ +--------+
^
|
val list1
这对应于 scala 中的链式“cons”(列表构造)调用:
scala> val list1 = 1 :: 3 :: Nil
list1: List[Int] = List(3, 1)
List 是一个不可变的数据结构。我们不能通过重写任何内容来破坏性地更新它
现有的指针/引用。那么如果我们想在前面添加呢?
scala> val list2 = 0 :: list1
list2: List[Int] = List(0, 1, 3)
创建 list2 非常快速和简单,我们只需创建一个新的 cons 单元格并将其添加到现有列表之前:
+------+------+ +------+------+ +------+------+ +--------+
| 0 | *--+----->| 1 | * -+----> | 3 + * -+--->| NIL |
+------+------+ +------+------+ +-------------+ +--------+
^ ^
| |
val list2 val list1
这样做的美妙之处在于它保持所有现有引用不变,保持不变性。但是,如果我们想 /append/ 到列表中,我们必须将 Nil 的引用覆盖到一个新元素,从而改变 list1。因此,唯一的方法是复制整个现有列表并重写它:
scala> val list3 = list1 ++ (5 :: Nil)
list3: List[Int] = List(1, 3, 5)
+------+------+ +------+------+ +------+------+ +--------+
| 0 | *--+----->| 1 | * -+----> | 3 + * -+--->| NIL |
+------+------+ +------+------+ +-------------+ +--------+
^ ^ ^
| | |
val list2 val list1 |
|
|
(copy) |
+------+------+ +------+------+ +--------+-------+ |
| 1 | * -+----> | 3 + * -+--->| 5 | *--+-------+
+------+------+ +-------------+ +--------+-------+
^
|
val list3
这是一个线性时间操作,因为要附加到长度为 N 的列表,我们必须复制所有 N 个 cons 单元格(Nil 除外)。
至于 scala 的 ListBuffer,它的 append 通过直接改变后备列表、重写最终 cons 单元格的尾部引用来工作,该单元格的类型看起来有点尴尬::[A]。
在 List.scala 中:
final case class ::[B](override val head: B, private[scala] var tl: List[B]) extends List[B] {
override def tail : List[B] = tl
override def isEmpty: Boolean = false
}
[..]
在 ArrayList.scala 中:
/** Appends a single element to this buffer. This operation takes constant time.
*
* @param x the element to append.
* @return this $coll.
*/
def += (x: A): this.type = {
if (exported) copy()
if (isEmpty) {
last0 = new :: (x, Nil)
start = last0
} else {
val last1 = last0
last0 = new :: (x, Nil)
last1.tl = last0
}
len += 1
this
}
last1.tl = last0 是允许的,因为 ::.tl 具有可见性 private[scala],允许不安全的“幕后”操作来改变集合,例如 ListBuffer。