Friday, April 26, 2024
HomeGolangConcatenate strings in Go (Golang)

Concatenate strings in Go (Golang)



In Go, there are a number of strategies for string concatenation. The best one (however not the most effective one) is so as to add two strings utilizing the plus (+) operator. You may also use the fmt.Sprintf() operate in case you have a few strings that need to mix into one. A extra environment friendly technique to concatenate strings is to make use of the strings.Builder construction which minimizes reminiscence copying and is really useful for constructing longer outcomes. Alternatively, you may also use the strings.Be a part of() operate if all of the strings to hitch and their separator earlier than calling it, or bytes.Buffer and byte slices to function immediately on string bytes.


There isn’t a single greatest methodology to concatenate strings, so it’s a good suggestion to know most of them, their professionals and cons, and use them relying in your use case.

All benchmarks within the examples beneath have been run in Go 1.17.

Use the plus (+) operator to easily concatenate strings

Utilizing the plus (+) operator is the best means of strings concatenation. Nevertheless, you ought to be cautious when calling this methodology as a result of strings in Go are immutable, and each time you add a string to an current variable, a brand new string is allotted in reminiscence. Because of this, this methodology is inefficient if it’s good to concatenate a number of strings, for instance, in a loop. In that case, it’s best to use the strings.Builder methodology to construct the ultimate string.

This and all the next examples give the output: Good day https://gosamples.dev 😉.

package deal predominant

import "fmt"

func predominant() {
    howdy := "Good day"
    gosamples := "https://gosamples.dev"

    outcome := howdy
    outcome += " "
    outcome += gosamples
    fmt.Println(outcome)
}

Professionals and cons of concatenating strings utilizing the plus (+) operator:


  • The best means of concatenating strings

  • No want to make use of exterior dependencies


  • Inefficient when used to concatenate an extended listing of strings
  • Much less readable than fmt.Sprintf() methodology when constructing a formatted string

Use the fmt.Sprintf() operate to format a number of strings into one

The fmt.Sprintf() operate takes a template and arguments and returns this template with the suitable fields changed by the arguments. That is probably the most idiomatic and readable methodology for creating quick strings with variable values however will not be appropriate for concatenation whenever you have no idea the variety of strings prematurely.

package deal predominant

import "fmt"

func predominant() {
    howdy := "Good day"
    gosamples := "https://gosamples.dev"

    outcome := fmt.Sprintf("%s %s", howdy, gosamples)
    fmt.Println(outcome)
}

Professionals and cons of concatenating strings utilizing the fmt.Sprintf() operate:


  • A transparent and idiomatic means of making strings with variable values

  • Permits to simply create strings from arguments of various sorts e.g. string, int, bool, and so forth., with out express conversion


  • Not appropriate whenever you have no idea prematurely the variety of components to concatenate
  • Inconvenient for an extended listing of arguments

Use strings.Builder to effectively concatenate strings

The strings.Builder was created to construct lengthy strings in a easy and environment friendly means. This methodology minimizes the variety of copies and reminiscence allocations and works significantly effectively in case you have a big listing of strings to concatenate or if the method of constructing the ensuing string is multi-step. If it’s good to carry out string concatenation effectively, this methodology is probably the most really useful and pure alternative in Go.

package deal predominant

import (
    "fmt"
    "log"
    "strings"
)

func predominant() {
    information := []string{"Good day", " ", "https://gosamples.dev"}

    var builder strings.Builder
    for _, s := vary information {
        _, err := builder.WriteString(s)
        if err != nil {
            log.Deadly(err)
        }
    }

    fmt.Println(builder.String())
}

If the scale of the output prematurely, it’s a good follow to make use of the Develop() methodology to preallocate the wanted reminiscence. This will increase the velocity of concatenation by avoiding pointless copying of partial outcomes:

Professionals and cons of concatenating strings utilizing the strings.Builder:


  • Environment friendly for concatenating an extended listing of strings or for constructing a string in a number of steps


  • Extra sophisticated to make use of than the earlier strategies

Use the strings.Be a part of() operate to create a single string from a slice

The strings.Be a part of() constructs a single string from becoming a member of a set slice of strings with an outlined separator between them. It makes use of the strings.Builder internally. For the reason that variety of components to be concatenated is understood, it allocates the required quantity of reminiscence, which makes this methodology very environment friendly.

package deal predominant

import (
    "fmt"
    "strings"
)

func predominant() {
    howdy := "Good day"
    gosamples := "https://gosamples.dev"

    outcome := strings.Be a part of([]string{howdy, gosamples}, " ")
    fmt.Println(outcome)
}

Professionals and cons of concatenating strings utilizing the strings.Be a part of() operate:


  • Tremendous-efficient for concatenating a set listing of strings with the identical separator

  • Easy and simple to make use of


  • Not appropriate whenever you have no idea prematurely the variety of components to concatenate or if you wish to use totally different separators

Use bytes.Buffer to effectively construct a byte buffer

The strings.Builder was launched in Go 1.10. Earlier than that, the bytes.Buffer was used to concatenate strings effectively. It has comparable strategies however is barely slower, so within the new code, it’s best to use the strings.Builder as an alternative.

package deal predominant

import (
    "bytes"
    "fmt"
    "log"
)

func predominant() {
    information := []string{"Good day", " ", "https://gosamples.dev"}

    var buffer bytes.Buffer
    for _, s := vary information {
        _, err := buffer.WriteString(s)
        if err != nil {
            log.Deadly(err)
        }
    }

    fmt.Println(buffer.String())
}

As with the strings.Builder, you should utilize the Develop() methodology to preallocate the wanted reminiscence:

Professionals and cons of concatenating strings utilizing the bytes.Buffer:


  • Environment friendly for concatenating an extended listing of strings or for constructing a string in a number of steps

  • Since Go 1.10, there may be the strings.Builder which has comparable strategies and is extra environment friendly

Use byte slice to increase a string

Strings in Go are read-only slices of bytes, so there isn’t any downside extending a byte slice of a string by appending bytes of one other string. Because of this, after changing to a string, we get the concatenated output. Nevertheless, this methodology is low-level and fewer idiomatic than the others. In follow, it’s significantly better to make use of the strings.Builder as an alternative.

package deal predominant

import (
    "fmt"
)

func predominant() {
    information := []string{"Good day", " ", "https://gosamples.dev"}

    var byteSlice []byte
    for _, s := vary information {
        byteSlice = append(byteSlice, []byte(s)...)
    }

    fmt.Println(string(byteSlice))
}

Professionals and cons of concatenating strings utilizing appending to a byte slice:


  • Straightforward to make use of

  • Don’t require any dependencies

  • Works on byte slices as an alternative of strings – requires last conversion to string

  • Not as environment friendly because the bytes.Buffer

Benchmarks

To examine which methodology of concatenating strings is the quickest, we ready a easy benchmark that compares all of the above strategies. Every of them concatenates 399 components right into a single outcome. We simulated two variants of concatenation: after we know the scale of the outcome string prematurely (benchmarks named Benchmark<XXX>KnownSize), and after we have no idea the precise dimension of the outcome string (benchmarks named Benchmark<XXX>UnknownSize). We did this as a result of some strategies are solely appropriate after we know the variety of components to concatenate (strings.Be a part of(), fmt.Sprintf()), some work with out contemplating the variety of components (the plus (+) operator), and a few work in each variants (strings.Builder, bytes.Buffer, byte slice):

  • strings.Be a part of() requires a slice of strings as an argument, so the variety of components to concatenate should be identified earlier than calling this operate.
  • fmt.Sprintf() works for a finite and identified variety of arguments equal to the variety of arguments within the template.
  • the plus (+) operator concatenates just one argument at a time (outcome += argument), and you can’t specify what number of components you may have or preallocate a set quantity of reminiscence for the outcome.
  • strings.Builder and bytes.Buffer can construct strings after we have no idea the variety of components that may lastly be within the outcome. When needed, they allocate extra reminiscence for the output. Due to this fact, these strategies work effectively for creating strings dynamically, for instance, in a loop. Then again, after we know the variety of components to concatenate and the scale of the outcome, we will instantly allocate the wanted quantity of reminiscence utilizing the Develop() methodology (out there in strings.Builder in addition to in bytes.Buffer), avoiding pointless copying of partial outcomes.
  • byte slices, like strings.Builder and bytes.Buffer, could be initialized as empty utilizing var byteSlice []byte or with a specified capability utilizing byteSlice := make([]byte, 0, dimension). On this means, this methodology can be utilized each with a identified variety of components and with out specifying the scale of the outcome.

The quickest methodology doesn’t imply the most effective. All the time take into account the readability of the code and attempt to match the strategy to the use case.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package deal benchmark

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

const (
    howdy     = "howdy"
    gosamples = "https://gosamples.dev"
)

func generateStrings(withSeparator bool) (information []string, dimension int) {
    for i := 0; i < 100; i++ {
        information = append(information, howdy)
        dimension += len(howdy)
        if withSeparator {
            information = append(information, " ")
            dimension++
        }
        information = append(information, gosamples)
        dimension += len(gosamples)
        if withSeparator {
            information = append(information, " ")
            dimension++
        }
    }
    if withSeparator {
        information = information[:len(data)-1]
        dimension--
    }

    return information, dimension
}

func BenchmarkPlusOperatorUnknownSize(b *testing.B) {
    information, _ := generateStrings(true)
    var s string
    for n := 0; n < b.N; n++ {
        for _, d := vary information {
            s += d
        }
        _ = s
    }
}

func BenchmarkSprintfKnownSize(b *testing.B) {
    information, _ := generateStrings(false)
    var interfaceData []interface{}
    for _, d := vary information {
        interfaceData = append(interfaceData, d)
    }
    format := strings.Repeat("%s ", len(interfaceData))
    format = strings.TrimSuffix(format, " ")
    var s string
    for n := 0; n < b.N; n++ {
        s = fmt.Sprintf(format, interfaceData...)
        _ = s
    }
}

func BenchmarkStringBuilderUnknownSize(b *testing.B) {
    information, _ := generateStrings(true)
    var s string
    for n := 0; n < b.N; n++ {
        var builder strings.Builder
        for _, s := vary information {
            builder.WriteString(s)
        }
        s = builder.String()
        _ = s
    }
}

func BenchmarkStringBuilderKnownSize(b *testing.B) {
    information, dimension := generateStrings(true)
    var s string
    for n := 0; n < b.N; n++ {
        var builder strings.Builder
        builder.Develop(dimension)
        for _, s := vary information {
            builder.WriteString(s)
        }
        s = builder.String()
        _ = s
    }
}

func BenchmarkJoinKnownSize(b *testing.B) {
    information, _ := generateStrings(false)
    var s string
    for n := 0; n < b.N; n++ {
        s = strings.Be a part of(information, " ")
        _ = s
    }
}

func BenchmarkBytesBufferUnknownSize(b *testing.B) {
    information, _ := generateStrings(true)
    var s string
    for n := 0; n < b.N; n++ {
        var buffer bytes.Buffer
        for _, s := vary information {
            buffer.WriteString(s)
        }
        s = buffer.String()
        _ = s
    }
}

func BenchmarkBytesBufferKnownSize(b *testing.B) {
    information, dimension := generateStrings(true)
    var s string
    for n := 0; n < b.N; n++ {
        var buffer bytes.Buffer
        buffer.Develop(dimension)
        for _, s := vary information {
            buffer.WriteString(s)
        }
        s = buffer.String()
        _ = s
    }
}

func BenchmarkByteSliceUnknownSize(b *testing.B) {
    information, _ := generateStrings(true)
    var s string
    for n := 0; n < b.N; n++ {
        var byteSlice []byte
        for _, s := vary information {
            byteSlice = append(byteSlice, []byte(s)...)
        }
        s = string(byteSlice)
        _ = s
    }
}

func BenchmarkByteSliceKnownSize(b *testing.B) {
    information, dimension := generateStrings(true)
    var s string
    for n := 0; n < b.N; n++ {
        byteSlice := make([]byte, 0, dimension)
        for _, s := vary information {
            byteSlice = append(byteSlice, []byte(s)...)
        }
        s = string(byteSlice)
        _ = s
    }
}

Working the benchmark with the command:

we acquired the outcomes:

BenchmarkPlusOperatorUnknownSize-8           166          12008232 ns/op
BenchmarkSprintfKnownSize-8               184053              6421 ns/op
BenchmarkStringBuilderUnknownSize-8       269620              4365 ns/op
BenchmarkStringBuilderKnownSize-8         422790              2735 ns/op
BenchmarkJoinKnownSize-8                  475293              2370 ns/op
BenchmarkBytesBufferUnknownSize-8         219260              5441 ns/op
BenchmarkBytesBufferKnownSize-8           321973              3639 ns/op
BenchmarkByteSliceUnknownSize-8           175533              6803 ns/op
BenchmarkByteSliceKnownSize-8             230568              5046 ns/op

The benchmark can also be out there as a Github Gist.

  • As you possibly can see, the plus (+) operator is way slower than the opposite strategies, and you shouldn’t use them to concatenate an extended listing of components.
  • When it comes to efficiency, it’s best to make use of the strings.Builder or strings.Be a part of() to hitch an extended listing of strings. In our case, strings.Be a part of() turned out to be even quicker than the strings.Builder.
  • You get a giant speedup whenever you preallocate the quantity of reminiscence needed for the output.
  • The bytes.Buffer and byte slice strategies are slower and never as readable because the strings.Builder as a result of they function on bytes moderately than strings. So, they shouldn’t be your first alternative for string concatenation.
RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments