从
scala的2.12.8
current documentation开始,List的尾部是常量,ListBuffer的尾部是线性的.但是,查看
source code,看起来尾部函数没有覆盖,并且在大多数用例中(例如删除head元素),List的尾部函数被显式调用.由于ListBuffer似乎只是一个带有长度var和指向最后一个元素的指针的List包装器,为什么它是线性的?
我对两种方法进行了计时,看起来List的尾部是常量,而ListBuffer的尾部确实是线性的:
import scala.collection.mutable import scala.collection.immutable val immutableList: immutable.List[Int] = (1 to 10000).toList val mutableListBuffer: mutable.ListBuffer[Int] = mutable.ListBuffer.empty[Int] ++= (1 to 10000).toList // warm-up (1 to 100000).foreach(_ => immutableList.tail) (1 to 100000).foreach(_ => mutableListBuffer.tail) // Measure val start = System.nanoTime() (1 to 1000).foreach(_ => immutableList.tail) val middle = System.nanoTime() (1 to 1000).foreach(_ => mutableListBuffer.tail) val stop = System.nanoTime() println((middle - start) / 1000) println((stop - middle) / 1000)
结果如:
1076 86010
但是,如果使用诸如remove(0)之类的函数使用List的尾部,则它是常量,具有以下结果:
1350 1724
解决方法
ListBuffer不扩展List,并且它不覆盖tail的事实并不意味着它使用List#tail.如果你看一下
tail
on ListBuffer
文档的定义类部分,你会看到它来自TraversableLike,它的定义如下:
override def tail: Repr = { if (isEmpty) throw new UnsupportedOperationException("empty.tail") drop(1) }
如果你看一下drop,你会发现它使用一个构建器来构造一个包含除第一个元素之外的所有元素的新集合,这解释了为什么它是线性的.
正如talex在上面的注释中暗示的那样,ListBuffer#tail必须返回一个新的集合,因为原始缓冲区可以被修改,标准库设计者已经决定你不希望那些修改反映在你从尾部获得的结果中.