周围的Scala字符串

时间:2011-12-02 06:55:30

标签: string scala immutability stringbuilder string-concatenation

如果您在单个语句中执行某些操作,例如“abc”+ stringval +“abc”,那么是一个不可变的字符串副本,或者两个(注意到abc和123在编译时是常量)

Bonus round:使用像下面这样的StringBuilder会有多少开销?

  def surround(s:String, ss:String):String = {
    val surrounded = new StringBuilder(s.length() + 2*ss.length(), s)
    surrounded.insert(0,ss)
    surrounded.append(ss)
    surrounded.mkString
  }

还是有一种我不知道的更惯用的方式吗?

6 个答案:

答案 0 :(得分:6)

它比连接的开销少。但是示例中的插入效率不高。以下是一个更清洁,只使用追加效率。

def surround(s:String, ss:String) =
  new StringBuilder(s.length() + 2*ss.length(), ss).append(s).append(ss).mkString

答案 1 :(得分:3)

我的第一个冲动是查看字节码并查看。所以,

// test.scala
object Comparison {
  def surround1(s: String, ss: String) = {
    val surrounded = new StringBuilder(s.length() + 2*ss.length(), s)
    surrounded.insert(0, ss)
    surrounded.append(ss)
    surrounded.mkString
  }

  def surround2(s: String, ss: String) = ss + s + ss 

  def surround3(s: String, ss: String) =  // Neil Essy
    new StringBuilder(s.length() + 2*ss.length(), ss).append(s).append(ss).mkString
}

然后:

$ scalac -optimize test.scala
$ javap -verbose Comparison$
[... lots of output ...]

粗略地说,Neil Essy和你的相同但是对于一个方法调用(和一些堆栈噪声)。 surround2被编译成类似

的内容
val sb = new StringBuilder()
sb.append(ss)
sb.append(s)
sb.append(ss)
sb.toString

我是Scala(和Java)的新手,所以我不知道查看字节码有多普遍有用 - 但是它会告诉你这个 scalac使用代码。

答案 2 :(得分:2)

在Java中进行一些测试,现在在Scala中使用StringBuilder的值是值得怀疑的,除非你做了很多非常量字符串的附加。

object AppendTimeTest {
    val tries = 500000
    def surround(s:String, ss:String) = {
        (1 to tries).foreach(_ => {
            new StringBuilder(s.length() + 2*ss.length(), ss).append(s).append(ss).mkString
        })
        val start = System.currentTimeMillis()
        (1 to tries).foreach(_ => {
            new StringBuilder(s.length() + 2*ss.length(), ss).append(s).append(ss).mkString
        })
        val stop = System.currentTimeMillis()
        val delta:Double = stop -start
        println("Total time: " + delta + ".\n Avg. time: " + (delta/tries))
    }
    def surroundStatic(s:String) = {
        (1 to tries).foreach(_ => {
            "ABC" + s + "ABC"
        })

        val start = System.currentTimeMillis()
        (1 to tries).foreach(_ => {
            "ABC" + s + "ABC"
        })
        val stop = System.currentTimeMillis()

        val delta:Double = stop -start
        println("Total time: " + delta + ".\n Avg. time: " + (delta/tries))
    }
}

在解释器中调用几次会产生:

scala> AppendTimeTest.surroundStatic("foo")
Total time: 241.0.
 Avg. time: 4.82E-4

scala>  AppendTimeTest.surround("foo", "ABC")
Total time: 222.0.
 Avg. time: 4.44E-4

scala> AppendTimeTest.surroundStatic("foo")
Total time: 231.0.
 Avg. time: 4.62E-4

scala>  AppendTimeTest.surround("foo", "ABC")
Total time: 247.0.
 Avg. time: 4.94E-4

因此,除非你附加许多不同的非常量字符串,否则我相信你不会看到性能上的任何重大差异。 Alsol连接常量(即"ABC" + "foo" + "ABC")就像编译器所知道的那样(这至少是Java中的情况,但我相信Scala也适用)

答案 3 :(得分:1)

Scala非常接近java进行字符串操作。你的例子:

val stringval = "bar"
"abc" + stringval + "abc"

实际上以(在java风格中)结束为:

(new StringBuilder()).append("abc").append(stringval()).append("abc").toString()

这与java的行为相同,字符串之间的+通常会转换为StringBuilder的实例,效率更高。所以在这里,你正在为StringBuilder做三个副本,并创建一个最终的String,并且根据字符串的长度,可能有三个重新分配。

在你的例子中:

def surround(s:String, ss:String):String = {
  val surrounded = new StringBuilder(s.length() + 2*ss.length(), s)
  surrounded.insert(0,ss)
  surrounded.append(ss)
  surrounded.mkString
}

你的副本数量相同(3),但你只分配一次。

建议:如果字符串很小,使用+,它会产生很小的差异。如果字符串相对较大,则使用相关长度初始化StringBuilder,然后简单地追加。这对其他开发者来说更​​加清晰。

与性能一样,测量它,如果差异很小,请使用更简单的解决方案

答案 4 :(得分:1)

通常在Java / Scala中,源代码中的String文字是 interned 以提高效率,这意味着代码中的所有副本都将引用同一个对象。所以只有一个“副本”“abc”。

Java和Scala没有像C ++那样的“常量”。用val初始化的变量是不可变的,但在一般情况下,从一个实例到下一个实例的val不相同(通过构造函数指定)。

因此理论上编译器可以检查val将始终初始化相同值并进行相应优化的简单情况,但这会增加额外的复杂性。您可以通过将其写为“abc123abc”来自行优化。

其他人已经解决了你的奖金问题。

答案 5 :(得分:0)

您也可以在 //Returns 5 children cy.get('ul').children().toArray().reverse().forEach($el) => { $($el).click(); }); 上使用 mkString

String

在内部它也使用 def surround(s:String, ss:String) = s.mkString(ss, "", ss) ,但我喜欢它的读取方式。 我什至可能会内联它。