Как эффективно объединить строки в го

727

В Go, a stringявляется примитивным типом, что означает, что он доступен только для чтения, и каждая его манипуляция создаст новую строку.

Итак, если я хочу объединить строки много раз, не зная длины полученной строки, каков наилучший способ сделать это?

Наивным способом будет:

s := ""
for i := 0; i < 1000; i++ {
    s += getShortStringFromSomewhere()
}
return s

но это не кажется очень эффективным.

Рэнди Сугианто 'Юку'
источник
7
Еще одна скамейка
Иван Блэк
1
Примечание: этот вопрос и большинство ответов, кажется, были написаны до того, как append()пришли на язык, что является хорошим решением для этого. Он будет работать так же быстро, copy()но сначала будет увеличивать срез, даже если это означает выделение нового резервного массива, если емкости недостаточно. bytes.Bufferпо-прежнему имеет смысл, если вам нужны дополнительные удобные методы или ожидаемый пакет, который вы используете.
Томасруттер
7
Это не просто «кажется очень неэффективным»; у него есть особая проблема, с которой сталкивается каждый новый найм, не связанный с CS, в первые несколько недель работы. Это квадратично - O (n * n). Подумайте о последовательности чисел: 1 + 2 + 3 + 4 + .... Это n*(n+1)/2площадь треугольника основания n. Вы добавляете размер 1, затем размер 2, затем размер 3 и т. Д., Когда добавляете неизменные строки в цикле. Это квадратичное потребление ресурсов проявляется не только в этом.
Роб

Ответы:

856

Новый путь:

С Go 1.10 есть strings.Builderтип, пожалуйста, посмотрите на этот ответ для более подробной информации .

Старый Путь:

Используйте bytesпакет. У него есть Bufferтип, который реализует io.Writer.

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var buffer bytes.Buffer

    for i := 0; i < 1000; i++ {
        buffer.WriteString("a")
    }

    fmt.Println(buffer.String())
}

Это происходит за O (n) раз.

продавец
источник
24
вместо println (строка (buffer.Bytes ())); использование может просто сделать println (buffer.String ())
FigmentEngine
26
Вместо buffer := bytes.NewBufferString(""), вы можете сделать var buffer bytes.Buffer. Вам также не нужны эти точки с запятой :).
crazy2be
66
Невероятно быстро Сделано, чтобы в моей программе было несколько наивных строк со знаком «+» с 3 минут до 1,3 секунд .
Малкольм
10
+1 за "O (n) время"; Я думаю, что важно сделать больше замечаний, как это.
противоречит
8
Go 1.10 добавляет strings.Builder , который похож на bytes.Buffer, но быстрее, когда вашей конечной целью является строка.
Джош Блихер Снайдер
272

Наиболее эффективный способ объединения строк - использование встроенной функции copy. В моих тестах этот подход примерно в 3 раза быстрее, чем при использовании, bytes.Bufferи намного быстрее (~ 12 000 раз), чем при использовании оператора +. Кроме того, он использует меньше памяти.

Я создал контрольный пример, чтобы доказать это, и вот результаты:

BenchmarkConcat  1000000    64497 ns/op   502018 B/op   0 allocs/op
BenchmarkBuffer  100000000  15.5  ns/op   2 B/op        0 allocs/op
BenchmarkCopy    500000000  5.39  ns/op   0 B/op        0 allocs/op

Ниже приведен код для тестирования:

package main

import (
    "bytes"
    "strings"
    "testing"
)

func BenchmarkConcat(b *testing.B) {
    var str string
    for n := 0; n < b.N; n++ {
        str += "x"
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); str != s {
        b.Errorf("unexpected result; got=%s, want=%s", str, s)
    }
}

func BenchmarkBuffer(b *testing.B) {
    var buffer bytes.Buffer
    for n := 0; n < b.N; n++ {
        buffer.WriteString("x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); buffer.String() != s {
        b.Errorf("unexpected result; got=%s, want=%s", buffer.String(), s)
    }
}

func BenchmarkCopy(b *testing.B) {
    bs := make([]byte, b.N)
    bl := 0

    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        bl += copy(bs[bl:], "x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); string(bs) != s {
        b.Errorf("unexpected result; got=%s, want=%s", string(bs), s)
    }
}

// Go 1.10
func BenchmarkStringBuilder(b *testing.B) {
    var strBuilder strings.Builder

    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        strBuilder.WriteString("x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); strBuilder.String() != s {
        b.Errorf("unexpected result; got=%s, want=%s", strBuilder.String(), s)
    }
}
CD1
источник
6
Bytes.Buffer должен делать в основном то же самое, что и копия (с некоторой дополнительной бухгалтерией, я полагаю), и скорость ничем не отличается. Так что я бы использовал это :). Разница в том, что буфер начинается с 0 байтов, поэтому он должен перераспределяться (я думаю, это делает его немного медленнее). Хотя проще в использовании.
Актау
5
buffer.Write(в байтах) на 30% быстрее, чем buffer.WriteString. [полезно, если вы можете получить данные как []byte]
Дани-Бр
34
Обратите внимание, что результаты теста искажены и не являются достоверными. Различные тестовые функции будут вызываться с разными значениями b.N, и поэтому вы не сравниваете время выполнения одной и той же задачи, которую нужно выполнить (например, одна функция может добавлять 1,000строки, другая может добавлять, 10,000что может иметь большое значение в среднем время 1 добавления, BenchmarkConcat()например). Вы должны использовать один и тот же счетчик добавлений в каждом случае (конечно, нет b.N) и выполнять всю конкатенацию внутри тела forранжирования до b.N(то есть, 2 forвстроенных цикла ).
icza
18
Кроме того, контрольный показатель копирования искажается за счет явного игнорирования времени, которое занимает выделение, которое включено в другие контрольные показатели.
gha.st
6
Кроме того, эталонный тест копирования основан на знании длины результирующей строки.
Скарллот
228

В Go 1.10+ есть strings.Builder, здесь .

Построитель используется для эффективного построения строки с использованием методов записи. Это минимизирует копирование памяти. Нулевое значение готово к использованию.


пример

Это почти то же самое с bytes.Buffer.

package main

import (
    "strings"
    "fmt"
)

func main() {
    // ZERO-VALUE:
    //
    // It's ready to use from the get-go.
    // You don't need to initialize it.
    var str strings.Builder

    for i := 0; i < 1000; i++ {
        str.WriteString("a")
    }

    fmt.Println(str.String())
}

Нажмите, чтобы увидеть это на детской площадке .


Запись

  • Не копируйте значение StringBuilder, поскольку оно кэширует базовые данные.
  • Если вы хотите поделиться значением StringBuilder, используйте указатель на него.

Поддерживаемые интерфейсы

Методы StringBuilder реализуются с учетом существующих интерфейсов. Так что вы можете легко переключаться на новый тип Builder в своем коде.


Отличия от байтов. Буфер

  • Он может только расти или сбрасываться.

  • Он имеет встроенный механизм copyCheck, который предотвращает случайное копирование:

    func (b *Builder) copyCheck() { ... }

  • В bytes.Buffer, можно получить доступ основных данных , например , следующим образом : (*Buffer).Bytes().

    • strings.Builder предотвращает эту проблему.
    • Иногда это не проблема, хотя и желательно вместо этого.
    • Например: для режима просмотра, когда байты передаются в и io.Readerт. Д.

Проверьте его исходный код для более подробной информации, здесь .

Инан Гумус
источник
5
Что вы подразумеваете под «побег»? Вы имеете в виду экранирование в строке, или только то, что нижележащие байты могут быть выставлены?
Махдуми
1
@makhdumi Да, во-вторых, раскрытие основных байтов.
Inanc Gumus
Стоит отметить, что strings.Builderреализует свои методы, используя указатель приемника, который бросил меня на мгновение. В результате я, вероятно, создал бы один, используя new.
Дункан Джонс
@DuncanJones Я добавил примечание, так как оно используется в основном для кэширования данных, это нормально использовать указатель на него при совместном использовании его между функциями и т. Д. В той же функции вы также можете использовать ее как не указатель.
Inanc Gumus
130

В пакете строк есть библиотечная функция Join: http://golang.org/pkg/strings/#Join

Взгляд на код Joinпоказывает аналогичный подход к функции добавления. Кинопико пишет: https://golang.org/src/strings/strings.go#L420.

Применение:

import (
    "fmt";
    "strings";
)

func main() {
    s := []string{"this", "is", "a", "joined", "string\n"};
    fmt.Printf(strings.Join(s, " "));
}

$ ./test.bin
this is a joined string
mbarkhau
источник
21
Не работает, когда вам нужно перебрать что-то, что не является строкой [].
Малкольм
42

Я только что проверил верхний ответ, опубликованный выше, в своем собственном коде (рекурсивное обход дерева) и простой оператор concat на самом деле быстрее, чем 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 секунды, тогда как следующий код:

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
} 

заняло всего 0,61 секунды. Вероятно, это связано с накладными расходами на создание нового 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,"")
}
JasonMc
источник
5
Я полагаю, что OP больше заботился о сложности памяти, а не о сложности времени выполнения, учитывая тот факт, что наивные конкатенации строк каждый раз приводят к новому выделению памяти.
Галактор
15
Медленная скорость этого вполне может быть связана с использованием fmt.Fprint вместо buffer.WriteString("\t"); buffer.WriteString(subs[i]);
Роберт Джек Уилл
Я рад узнать, что мой предпочитаемый метод (strings.Join)бега - самый быстрый, потому что это говорит о том, что (bytes.Buffer)он победитель!
Четабахана,
23

Вы можете создать большой кусок байтов и скопировать байты коротких строк в него, используя строковые фрагменты. В «Effective Go» есть функция:

func Append(slice, data[]byte) []byte {
    l := len(slice);
    if l + len(data) > cap(slice) { // reallocate
        // Allocate double what's needed, for future growth.
        newSlice := make([]byte, (l+len(data))*2);
        // Copy data (could use 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 ( )большой кусок байтов, чтобы снова преобразовать его в строку.


источник
Интересно, что в Go так много способов сделать это.
Ицхак
11
На самом деле, это также говорит о том, что идея настолько полезна, что была записана во встроенном. Таким образом, вы можете заменить свою функцию append(slice, byte...), кажется.
Актау
23

Это самое быстрое решение, которое не требует, чтобы вы сначала знали или рассчитали общий размер буфера:

var data []byte
for i := 0; i < 1000; i++ {
    data = append(data, getShortStringFromSomewhere()...)
}
return string(data)

По моим оценкам , это на 20% медленнее, чем решение для копирования (8,1 нс на приложение, а не 6,72 нс), но все еще на 55% быстрее, чем использование байтов. Буфер.

Rog
источник
23
package main

import (
  "fmt"
)

func main() {
    var str1 = "string1"
    var str2 = "string2"
    out := fmt.Sprintf("%s %s ",str1, str2)
    fmt.Println(out)
}
Гарольд Рамос
источник
2
Добро пожаловать в переполнение стека! Найдите минутку, чтобы прочитать справку по редактированию в справочном центре. Форматирование в переполнении стека отличается от других сайтов.
Rizier123
2
Хотя этот фрагмент кода может решить вопрос, в том числе объяснение действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причин, по которым вы предлагаете код. Также постарайтесь не переполнять ваш код пояснительными комментариями, это снижает удобочитаемость кода и пояснений!
Rizier123
Простое решение 👍
Финн
22

Примечание добавлено в 2018 году

С Go 1.10 есть strings.Builderтип, пожалуйста, посмотрите на этот ответ для более подробной информации .

Ответ до 201x

Код теста @ cd1 и другие ответы неверны. b.Nне должен быть установлен в тестовой функции. Он устанавливается инструментом динамического тестирования, чтобы определить, стабильно ли время выполнения теста.

Функция эталонного теста должна запускать одинаковое b.Nвремя теста, и тест внутри цикла должен быть одинаковым для каждой итерации. Поэтому я исправляю это, добавляя внутренний цикл. Я также добавлю тесты для некоторых других решений:

package main

import (
    "bytes"
    "strings"
    "testing"
)

const (
    sss = "xfoasneobfasieongasbg"
    cnt = 10000
)

var (
    bbb      = []byte(sss)
    expected = strings.Repeat(sss, cnt)
)

func BenchmarkCopyPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        bs := make([]byte, cnt*len(sss))
        bl := 0
        for i := 0; i < cnt; i++ {
            bl += copy(bs[bl:], sss)
        }
        result = string(bs)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkAppendPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, cnt*len(sss))
        for i := 0; i < cnt; i++ {
            data = append(data, sss...)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        buf := bytes.NewBuffer(make([]byte, 0, cnt*len(sss)))
        for i := 0; i < cnt; i++ {
            buf.WriteString(sss)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkCopy(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, 64) // same size as bootstrap array of bytes.Buffer
        for i := 0; i < cnt; i++ {
            off := len(data)
            if off+len(sss) > cap(data) {
                temp := make([]byte, 2*cap(data)+len(sss))
                copy(temp, data)
                data = temp
            }
            data = data[0 : off+len(sss)]
            copy(data[off:], sss)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkAppend(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, 64)
        for i := 0; i < cnt; i++ {
            data = append(data, sss...)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferWrite(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var buf bytes.Buffer
        for i := 0; i < cnt; i++ {
            buf.Write(bbb)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferWriteString(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var buf bytes.Buffer
        for i := 0; i < cnt; i++ {
            buf.WriteString(sss)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkConcat(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var str string
        for i := 0; i < cnt; i++ {
            str += sss
        }
        result = str
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

Окружающая среда - OS X 10.11.6, 2,2 ГГц Intel Core i7

Результаты теста:

BenchmarkCopyPreAllocate-8         20000             84208 ns/op          425984 B/op          2 allocs/op
BenchmarkAppendPreAllocate-8       10000            102859 ns/op          425984 B/op          2 allocs/op
BenchmarkBufferPreAllocate-8       10000            166407 ns/op          426096 B/op          3 allocs/op
BenchmarkCopy-8                    10000            160923 ns/op          933152 B/op         13 allocs/op
BenchmarkAppend-8                  10000            175508 ns/op         1332096 B/op         24 allocs/op
BenchmarkBufferWrite-8             10000            239886 ns/op          933266 B/op         14 allocs/op
BenchmarkBufferWriteString-8       10000            236432 ns/op          933266 B/op         14 allocs/op
BenchmarkConcat-8                     10         105603419 ns/op        1086685168 B/op    10000 allocs/op

Вывод:

  1. CopyPreAllocateсамый быстрый способ; AppendPreAllocateдовольно близко к № 1, но легче написать код.
  2. Concatимеет очень плохую производительность как по скорости, так и по использованию памяти. Не используйте это.
  3. Buffer#Writeи Buffer#WriteStringв основном одинаковы по скорости, вопреки тому, что @ Dani-Br сказал в комментарии. Учитывая, stringчто действительно []byteв Go, это имеет смысл.
  4. bytes.Buffer в основном использует то же решение, что и Copyс дополнительным бухгалтерским учетом и другими вещами.
  5. Copyи Appendиспользовать размер начальной загрузки 64, такой же, как байты. Буфер
  6. Appendиспользовать больше памяти и выделяет, я думаю, это связано с алгоритмом роста, который он использует. Это не растет память так быстро, как байты.

Предложение:

  1. Для простой задачи, такой как то, что хочет OP, я бы использовал Appendили AppendPreAllocate. Это достаточно быстро и просто в использовании.
  2. Если необходимо одновременно прочитать и записать буфер, используйте, bytes.Bufferконечно. Вот для чего он предназначен.
PickBoy
источник
13

Мое оригинальное предложение было

s12 := fmt.Sprint(s1,s2)

Но выше ответ, используя bytes.Buffer - WriteString () является наиболее эффективным способом.

Мое первоначальное предложение использует отражение и переключение типов. Смотрите (p *pp) doPrintи(p *pp) printArg
нет универсального интерфейса Stringer () для базовых типов, как я наивно думал.

По крайней мере, Sprint () внутренне использует bytes.Buffer. таким образом

`s12 := fmt.Sprint(s1,s2,s3,s4,...,s1000)`

является приемлемым с точки зрения распределения памяти.

=> Сцепление Sprint () может использоваться для быстрого вывода отладочной информации.
=> В противном случае используйте bytes.Buffer ... WriteString

Питер Бухманн
источник
8
Он не встроен и не эффективен.
peterSO
Импорт пакета (например, fmt) означает, что он не встроен. Это в стандартной библиотеке.
Малкольм
Это медленно только потому, что использует отражение своих аргументов. Это эффективно. В противном случае это не менее эффективно, чем объединение со
строками.
11

Расширение ответа cd1: вы можете использовать append () вместо copy (). Функция append () обеспечивает более продвинутые предварительные условия, стоит немного больше памяти, но экономит время. Я добавил еще две отметки вверху вашей. Беги локально с

go test -bench=. -benchtime=100ms

На моем ThinkPad T400s это дает:

BenchmarkAppendEmpty    50000000         5.0 ns/op
BenchmarkAppendPrealloc 50000000         3.5 ns/op
BenchmarkCopy           20000000        10.2 ns/op
Питер Бухманн
источник
4

Это актуальная версия теста, предоставляемая @ 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
Виталий Исаев
источник
Я не думаю, что ручная настройка bN - это правильный способ использовать тестовые функции пакета тестирования
PickBoy
@PickBoy, пожалуйста, обоснуйте свою точку зрения. Как вы думаете, почему b.Nпубличная переменная?
Виталий Исаев
1
bN не должен быть установлен в контрольной функции. Он устанавливается инструментом динамического тестирования. Функция бенчмарка должна запускать один и тот же тест bN раз, но в вашем коде (а также в коде @ cd1) каждый тест в цикле - это отдельный тест (поскольку длина строки увеличивается)
PickBoy
@PickBoy, если вы позволите b.Nдинамическому набору инструмента тестирования , вы получите строки различной длины в разных тестах. Смотрите комментарий
Виталий Исаев
Вот почему вы должны добавить внутренний цикл с фиксированным числом итераций, например 10000, внутри цикла bN.
PickBoy
3

goutils.JoinBetween

 func JoinBetween(in []string, separator string, startIndex, endIndex int) string {
    if in == nil {
        return ""
    }

    noOfItems := endIndex - startIndex

    if noOfItems <= 0 {
        return EMPTY
    }

    var builder strings.Builder

    for i := startIndex; i < endIndex; i++ {
        if i > startIndex {
            builder.WriteString(separator)
        }
        builder.WriteString(in[i])
    }
    return builder.String()
}
Сиань Шу
источник
1

Я делаю это, используя следующее: -

package main

import (
    "fmt"
    "strings"
)

func main (){
    concatenation:= strings.Join([]string{"a","b","c"},"") //where second parameter is a separator. 
    fmt.Println(concatenation) //abc
}
Криш Бханушали
источник
Это не решает проблему OP построения строки через серию итераций с циклом for.
Codeforester
1
package main

import (
"fmt"
)

func main() {
    var str1 = "string1"
    var str2 = "string2"
    result := make([]byte, 0)
    result = append(result, []byte(str1)...)
    result = append(result, []byte(str2)...)
    result = append(result, []byte(str1)...)
    result = append(result, []byte(str2)...)

    fmt.Println(string(result))
}
Раджни Кант
источник
3
Пожалуйста, не публикуйте только ответы. Пожалуйста, объясните, что делает этот код и почему это решение.
Корашен
-1

результат теста со статистикой выделения памяти. проверьте код теста на github .

использовать строки. Builder для оптимизации производительности.

go test -bench . -benchmem
goos: darwin
goarch: amd64
pkg: github.com/hechen0/goexp/exps
BenchmarkConcat-8                1000000             60213 ns/op          503992 B/op          1 allocs/op
BenchmarkBuffer-8               100000000               11.3 ns/op             2 B/op          0 allocs/op
BenchmarkCopy-8                 300000000                4.76 ns/op            0 B/op          0 allocs/op
BenchmarkStringBuilder-8        1000000000               4.14 ns/op            6 B/op          0 allocs/op
PASS
ok      github.com/hechen0/goexp/exps   70.071s
hechen0
источник
Пожалуйста, отдайте должное @ cd1 за оригинальные тестовые примеры, на которых вы основываетесь.
colm.anseo
-2
s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))
user2288856
источник
5
Это очень медленное решение, потому что оно использует отражение, анализирует строку формата и создает копию данных для []byte(s1)преобразования. Сравнивая это с другими опубликованными решениями, можете ли вы назвать одно преимущество вашего решения?
оч
-5

strings.Join() из пакета "Струны"

Если у вас есть несоответствие типов (например, если вы пытаетесь соединить int и строку), вы делаете RANDOMTYPE (вещь, которую вы хотите изменить)

EX:

package main

import (
    "fmt"
    "strings"
)

var intEX = 0
var stringEX = "hello all you "
var stringEX2 = "people in here"


func main() {
    s := []string{stringEX, stringEX2}
    fmt.Println(strings.Join(s, ""))
}

Вывод :

hello all you people in here
Лиам
источник
4
Этот код даже не компилируется: strings.Join()принимает только 2 параметра: срез и разделитель string.
icza
это не может помочь
Anshu
добавить некоторые изменения здесь.
Anshu