1

如果你喜欢“ABC” + stringval +“ABC”一条语句做的事,是一个不可改变的字符串拷贝,或两个(注意,ABC和123是在编译时间常数)周边斯卡拉字符串

奖金回合:使用类似以下的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

它有比串联更少的开销。但是在你的例子中插入是没有效率的。以下是一些简单的清洁工具,仅用于追求效率。

def surround(s:String, ss:String) = 
    new StringBuilder(s.length() + 2*ss.length(), ss).append(s).append(ss).mkString 
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 ...] 

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

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

我是新来斯卡拉(和Java),所以我不知道它是如何通常是有用的看字节码 - 但它告诉你什么这个scalac这个代码。

+2

surround2与surround3之间的主要区别在于,surround3预先计算了支持StringBuilder所需的数组大小。这消除了扩展支持构建器的数组的可能性和成本,其细节隐藏在StringBuilder类中。 –

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)

+0

您应该尝试使用总共16个字符以上的最终字符串长度,因为最初StringBuilder为0。 –

+0

我无耻地偷了@Neil Essy的追加代码:)。如果我用一个零参数替换StringBuilder并使用append添加字符串,我会得到类似的结果。也尝试增加静态字符串一点,即“ABCDEFGHIJKLMNOPQRSTUVXYZ” –

+0

@Luigi我意识到你的意思是尝试使用相同的代码,但字符串超过16个字符,而不是将StringBuilder更改为零参数(因为这是无论如何,在surroundStatic方法中幕后会发生什么)......这样做实际上会产生一些速度差,对于SurroundStatic方法,速度大约慢了1.6倍。 –

1

Scala与Java的字符串操作非常接近。你的榜样:

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

实际上结束了(在Java的方式)为:

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

这是相同的行为如Java,+字符串之间通常被翻译为StringBuilder的实例,这是更有效。所以在这里,你要为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 
} 

你正在做同样的份数(3),但你只能分配一次。

建议:如果字符串很小,请使用+,这样做几乎没有什么区别。如果字符串比较大,则用相应的长度初始化StringBuilder,然后简单追加。对于其他开发者来说这更清晰。

由于总是与性能,测量它,并且如果差异很小,在Java/Scala中使用简单的解决方案

1

通常,String文字在源代码中实习为了效率,这意味着所有副本它在你的代码中会引用同一个对象。所以只会有一个“abc”的“副本”。

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

所以在理论上,编译器可以检查val总是初始化相同的值并相应地进行优化的简单情况,但这会增加额外的复杂性。你可以通过写成“abc123abc”来自己优化它。

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