Як ефективно об'єднати рядки в дорозі


727

У Go, a string- це примітивний тип, що означає, що він доступний лише для читання, і кожна маніпуляція з ним створюватиме новий рядок.

Отже, якщо я хочу багато разів об'єднати рядки, не знаючи довжини отриманого рядка, який найкращий спосіб це зробити?

Наївним шляхом було б:

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

але це здається не дуже ефективним.



1
Примітка. Це питання та більшість відповідей, здається, були написані ще append()до появи мови, що є хорошим рішенням для цього. Він буде працювати швидко, copy()але спочатку зросте фрагмент, навіть якщо це означає виділення нового резервного масиву, якщо ємність недостатня. bytes.Bufferвсе ще має сенс, якщо ви хочете отримати його додаткові методи зручності або якщо пакет, який ви використовуєте, очікує цього.
thomasrutter

7
Це не просто "здається дуже неефективним"; у нього є специфічна проблема, що кожен новий найманий персонал, який ми коли-небудь отримували, стикається в перші кілька тижнів на роботі. Це квадратично - 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
Перейти до 1.10 додає strings.Builder , який схожий на bytes.Buffer, але швидше, коли вашою кінцевою метою є рядок.
Джош Блічер Снайдер

272

Найефективніший спосіб об'єднання рядків - це використання вбудованої функції copy. У моїх тестах такий підхід на ~ 3 рази швидше, ніж використання, bytes.Bufferі набагато набагато швидше (~ 12 000x), ніж використання оператора +. Крім того, він використовує менше пам'яті.

Я створив тестовий випадок, щоб довести це, і ось результати:

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)
    }
}

6
Байти.Buffer повинні робити в основному те саме, що копія (з певною бухгалтерію я думаю), і швидкість не така вже й інша. Тож я би скористався цим :). Різниця полягає в тому, що буфер починається з 0 байт, тому він повинен перерозподілятись (це, здається, здається трохи повільніше). Однак простіше у використанні.
Актау

5
buffer.Write(Байт) становить 30% швидше , ніж buffer.WriteString. [корисно, якщо ви можете отримати дані як []byte]
Dani-Br

34
Зауважте, що результати еталонів спотворені і не є достовірними. Викликатимуться різні функції орієнтиру з різними значеннями b.N, і тому ви не порівнюєте час виконання однієї і тієї ж задачі, яку слід виконати (наприклад, одна функція може додавати 1,000рядки, інша може додаватись, 10,000що може значно змінити середнє значення час 1 додавання, BenchmarkConcat()наприклад). Ви повинні використовувати одне і те ж число додавання у кожному випадку (звичайно, ні b.N), і виконувати все конкатенацію всередині тіла, forпочинаючи з b.N(тобто forвбудовані 2 петлі).
ікза

18
Крім того, орієнтир копіювання скасовується шляхом явного ігнорування часу, який займає розподіл, який включений до інших орієнтирів.
gha.st

6
Крім того, орієнтир копіювання покладається на знання довжини отриманого рядка.
Скарлот

227

У Go 1.10+ є strings.Builder, ось .

Builder використовується для ефективної побудови рядка за допомогою методів Write. Це мінімізує копіювання пам'яті. Нульове значення готове до використання.


Приклад

Це майже те саме з 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 Так, 2-е, оголення базових байтів.
Інанк Gumus

Варто зазначити, що strings.Builderреалізує свої методи за допомогою приймача вказівника, який кинув мене на мить. Як результат, я, мабуть, створив би одну, використовуючи new.
Дункан Джонс

@DuncanJones Я додав ноту, однак, оскільки вона використовується в основному для кешування даних, звичайно використовувати вказівник на неї при обміні ними між функціями тощо.
Inanc Gumus

130

У пакеті рядків є функція бібліотеки під назвою Join: http://golang.org/pkg/strings/#Join

Погляд на код Joinпоказує аналогічний підхід до функції додавання Kinopiko написав: 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

21
Не працює, коли вам доведеться переводити цикл на щось, що не є рядком [].
Малькольм

42

Я просто відзначив верхню відповідь, розміщену вище у своєму власному коді (рекурсивна прогулянка по дереву), і простий оператор-конмат насправді швидше, ніж 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,"")
}

5
Я вважаю, що ОП більше хвилювалась складність пам’яті, а не складність виконання, враховуючи той факт, що наївні рядкові конкатенації щоразу призводять до нових розподілів пам’яті.
галактор

15
Повільна швидкість цього може бути пов'язана з використанням fmt.Fprint замістьbuffer.WriteString("\t"); buffer.WriteString(subs[i]);
Роберт Джек буде

Я радий знати, що мій вподобаний метод (strings.Join)бігу як найшвидший, з цього приказу, що (bytes.Buffer)переможець!
Четабахана

23

Ви можете створити великий фрагмент байтів і скопіювати в нього байти коротких рядків, використовуючи фрагменти рядків. У "Ефективний перехід" є функція:

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,1ns за додаток, а не 6,72ns), але все ж на 55% швидше, ніж використання байтів.Buffer.


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 GHz 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, такий самий, як і байти.Buffer
  6. Appendвикористовуйте більше пам'яті та алоків, я думаю, що це пов'язано з алгоритмом росту, який він використовує. Ця пам’ять не росте так швидко, як байти

Пропозиція:

  1. Для простих завдань, таких як те, що хоче ОП, я б використав Appendабо AppendPreAllocate. Це досить швидко і просто у використанні.
  2. Якщо вам потрібно читати і писати буфер одночасно, використовуйте bytes.Bufferзвичайно. Ось для чого він розрахований.

13

Моя оригінальна пропозиція була

s12 := fmt.Sprint(s1,s2)

Але вище відповідь за допомогою bytes.Buffer - WriteString () - це найефективніший спосіб.

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

Принаймні, хоча Sprint () внутрішньо використовує байти. Buffer. Таким чином

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

є прийнятним з точки зору розподілу пам'яті.

=> Сполучення Sprint () може використовуватися для швидкого виводу налагодження.
=> В іншому випадку використовуйте байти. Буфер ... WriteString


8
Він не вбудований і не є ефективним.
peterSO

Імпорт пакета (наприклад, fmt) означає, що він не вбудований. Це в стандартній бібліотеці.
Малькольм

Це повільно лише тому, що він використовує роздуми над своїми аргументами. Це ефективно. Інакше це не менш ефективно, ніж з'єднання з strings.Join
ithkuil

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.JoinBet between

 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
}

Це не стосується питання ОП щодо створення рядка через низку ітерацій з циклом 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 .

використовуйте strings.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

будь ласка, надайте кредит @ cd1 за оригінальні тестові випадки, які ви будуєте тут.
colm.anseo

-2
s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))

5
Це рішення дуже повільне, оскільки воно використовує відображення, воно аналізує рядок формату і робить копію даних для []byte(s1)перетворення. Порівнюючи це з іншими розміщеними рішеннями, чи можете ви назвати одну перевагу свого рішення?
пт

-5

strings.Join() з пакету "strings"

Якщо у вас невідповідність типу (наприклад, якщо ви намагаєтеся з'єднати int та string), ви робите 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()бере лише два параметри: фрагмент та роздільник string.
icza

це не може допомогти
Anshu

додати сюди деякі зміни.
Anshu
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.