Как эффективно объединять строки в Go
В Go тип string
является примитивным, что означает, что он является неизменяемым, и любое его изменение приводит к созданию новой строки.
Я хочу объединить строки множество раз, не зная длину результирующей строки. Какой самый эффективный способ сделать это?
Наивный способ может выглядеть так:
var s string
for i := 0; i < 1000; i++ {
s += getShortStringFromSomewhere()
}
return s
Однако это не кажется очень эффективным решением. Как лучше поступить в данной ситуации?
5 ответ(ов)
Если у вас есть срез строк, который вы хотите эффективно преобразовать в строку, вы можете использовать этот подход. В противном случае, обратите внимание на другие ответы.
В пакете строк strings
есть функция Join
:
http://golang.org/pkg/strings/#Join
Посмотрев на код Join
, можно увидеть, что он использует подход, похожий на функцию Append
, написанную Kinopiko: https://golang.org/src/strings/strings.go#L420
Пример использования:
import (
"fmt"
"strings"
)
func main() {
s := []string{"это", "соединённая", "строка\n"}
fmt.Printf(strings.Join(s, " "))
}
При выполнении программы вы получите:
это соединённая строка
При тестировании кода на производительность, I обнаружил, что использование простого оператора конкатенации строк быстрее, чем BufferString
, в контексте рекурсивного обхода дерева.
Вот пример с использованием BufferString
:
func (r *record) String() string {
buffer := bytes.NewBufferString("")
fmt.Fprint(buffer,"(",r.name,"[")
for i := 0; i < len(r.subs); i++ {
fmt.Fprint(buffer,"\t",r.subs[i])
}
fmt.Fprint(buffer,"]",r.size,")\n")
return buffer.String()
}
Этот код выполнялся за 0.81 секунды, в то время как следующий код, использующий конкатенацию строк, выполнялся быстрее — за 0.61 секунды:
func (r *record) String() string {
s := "(\"" + r.name + "\" ["
for i := 0; i < len(r.subs); i++ {
s += r.subs[i].String()
}
s += "] " + strconv.FormatInt(r.size,10) + ")\n"
return s
}
Это, вероятно, связано с накладными расходами на создание нового BufferString
.
Обновление: Я также протестировал функцию join
, и она показала результат 0.54 секунды:
func (r *record) String() string {
var parts []string
parts = append(parts, "(\"", r.name, "\" [" )
for i := 0; i < len(r.subs); i++ {
parts = append(parts, r.subs[i].String())
}
parts = append(parts, strconv.FormatInt(r.size,10), ")\n")
return strings.Join(parts,"")
}
Таким образом, если вам важна производительность, стоит рассмотреть использование простого оператора конкатенации или, альтернативно, метода join
.
Ваш код на Go правильно формирует строку, используя функцию fmt.Sprintf
. Давайте я объясню, что здесь происходит:
package main
import (
"fmt"
)
func main() {
var str1 = "string1" // Объявляем переменную str1 и инициализируем её строкой "string1"
var str2 = "string2" // Объявляем переменную str2 и инициализируем её строкой "string2"
// Используем fmt.Sprintf для форматирования строки с использованием переменных str1 и str2
out := fmt.Sprintf("%s %s ", str1, str2)
// Выводим результат на экран
fmt.Println(out) // Вывод: "string1 string2 "
}
В этом примере fmt.Sprintf
формирует новую строку, вставляя значения str1
и str2
в заданный формат. Шаблон "%s %s "
означает, что будут вставлены две строки (по одной для каждой %s
), разделенные пробелом. Итоговая строка будет содержать содержимое обеих переменных, а затем выводится на экран.
Если вы хотите убрать лишний пробел в конце, просто измените шаблон на "%s %s"
без пробела в конце.
Вы можете создать большой срез байт и скопировать байты коротких строк в него, используя срезы строк. В "Effective Go" есть функция, которая поможет вам с этим:
func Append(slice, data []byte) []byte {
l := len(slice)
if l + len(data) > cap(slice) { // перераспределение памяти
// Выделяем в два раза больше, чем нужно, для будущего роста.
newSlice := make([]byte, (l+len(data))*2)
// Копируем данные (можно использовать bytes.Copy()).
for i, c := range slice {
newSlice[i] = c
}
slice = newSlice
}
slice = slice[0:l+len(data)]
for i, c := range data {
slice[l+i] = c
}
return slice
}
После завершения операций вы можете использовать string()
для преобразования большого среза байт обратно в строку.
Это актуальная версия бенчмарка, предоставленная @cd1 (на Go 1.8
, linux x86_64
), с исправлениями ошибок, упомянутыми @icza и @PickBoy.
Bytes.Buffer
оказывается всего лишь в 7
раз быстрее прямой конкатенации строк с помощью оператора +
.
Вот код бенчмарка:
package performance_test
import (
"bytes"
"fmt"
"testing"
)
const (
concatSteps = 100
)
func BenchmarkConcat(b *testing.B) {
for n := 0; n < b.N; n++ {
var str string
for i := 0; i < concatSteps; i++ {
str += "x"
}
}
}
func BenchmarkBuffer(b *testing.B) {
for n := 0; n < b.N; n++ {
var buffer bytes.Buffer
for i := 0; i < concatSteps; i++ {
buffer.WriteString("x")
}
}
}
Результаты:
BenchmarkConcat-4 300000 6869 ns/op
BenchmarkBuffer-4 1000000 1186 ns/op
Как видно из результатов, использование bytes.Buffer
значительно снижает время выполнения при многократной конкатенации строк, однако разница не так велика, как можно было бы ожидать.
Объединение двух столбцов текста в DataFrame pandas
Как написать многострочные строки в Go?
Почему сравнение строк с помощью '==' и 'is' иногда дает разные результаты?
Как проверить, содержит ли строка подстроку?
Функции startsWith() и endsWith() в PHP