Окружающие Скала Струны - PullRequest
1 голос
/ 02 декабря 2011

Если вы делаете что-то в одном выражении, например «abc» + stringval + «abc», это одна или две неизменяемые строковые копии (с учетом того, что abc и 123 постоянны во время компиляции)

Bonusround: будет ли использование 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
  }

Или есть более идиоматический способ, о котором я не знаю?

Ответы [ 5 ]

6 голосов
/ 02 декабря 2011

Он имеет меньше накладных расходов, чем конкатенация .Но вставка в вашем примере неэффективна.Следующее немного чище и использует только добавляет эффективности.

def surround(s:String, ss:String) =
  new StringBuilder(s.length() + 2*ss.length(), ss).append(s).append(ss).mkString
3 голосов
/ 02 декабря 2011

Мой первый импульс - посмотреть на байт-код и посмотреть.Итак,

// 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 голосов
/ 02 декабря 2011

Небольшое тестирование этого в 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)

1 голос
/ 02 декабря 2011

Обычно в Java / Scala литералы String в исходном коде интернированы для эффективности, что означает, что все его копии в вашем коде будут ссылаться на один и тот же объект.Таким образом, будет только одна «копия» «abc».

Java и Scala не имеют «констант», как в C ++.Переменные, инициализированные val, являются неизменяемыми, но в общем случае значения от одного экземпляра к другому не совпадают (задается через конструктор).

Таким образом, теоретически компилятор может проверять простые случаи, когда значение valвсегда будет инициализировать одно и то же значение и соответственно оптимизировать, но это добавит дополнительную сложность.И вы можете просто оптимизировать его самостоятельно, написав его как «abc123abc».

Другие уже ответили на ваш бонусный вопрос.

1 голос
/ 02 декабря 2011

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 соответствующей длины, но затем просто добавьте.Это гораздо понятнее для других разработчиков.

Как всегда, с производительностью, измерьте ее, и, если различия невелики, используйте более простое решение

...