задний план
Во многих сценариях мы будем выполнять операции конкатенации строк.
В начале вы можете использовать следующие операции:
package main
func main() {
ss := []string{
"A",
"B",
"C",
}
var str string
for _, s := range ss {
str += s
}
print(str)
}
Как и многие языки, поддерживающие строковый тип, строковый тип в golang доступен только для чтения и неизменяем. Таким образом, этот способ объединения строк приводит к созданию, уничтожению большого количества строк и выделению памяти. Если вы объединяете много строк, это, очевидно, неправильная позиция.
До Golang 1.10 вы могли использоватьbytes.Buffer
Чтобы оптимизировать:
package main
import (
"bytes"
"fmt"
)
func main() {
ss := []string{
"A",
"B",
"C",
}
var b bytes.Buffer
for _, s := range ss {
fmt.Fprint(&b, s)
}
print(b.String())
}
использовать здесьvar b bytes.Buffer
Сохраните окончательную сплайсированную строку, чтобы в определенной степени избежать вышеперечисленного.str
Проблема повторного обращения за новым пространством памяти для хранения промежуточных строк каждый раз, когда выполняется операция склейки.
Но здесь все же есть небольшая проблема:b.String()
будет один раз[]byte -> string
Преобразование типов. Эта операция выполнит выделение памяти и копирование содержимого.
Конкатенация строк с использованием strings.Builder
Если вы уже используете golang 1.10, у вас есть лучший вариант:strings.Builder
:
package main
import (
"fmt"
"strings"
)
func main() {
ss := []string{
"A",
"B",
"C",
}
var b strings.Builder
for _, s := range ss {
fmt.Fprint(&b, s)
}
print(b.String())
}
Официальная воля Голангаstrings.Builder
Представлено как функция, должно быть две кисти. Не верите в счет? Вот простой бенчмарк:
package ts
import (
"bytes"
"fmt"
"strings"
"testing"
)
func BenchmarkBuffer(b *testing.B) {
var buf bytes.Buffer
for i := 0; i < b.N; i++ {
fmt.Fprint(&buf, "")
_ = buf.String()
}
}
func BenchmarkBuilder(b *testing.B) {
var builder strings.Builder
for i := 0; i < b.N; i++ {
fmt.Fprint(&builder, "")
_ = builder.String()
}
}
╰─➤ go test -bench=. -benchmem 2 ↵
goos: darwin
goarch: amd64
pkg: test/ts
BenchmarkBuffer-4 300000 101086 ns/op 604155 B/op 1 allocs/op
BenchmarkBuilder-4 20000000 90.4 ns/op 21 B/op 0 allocs/op
PASS
ok test/ts 32.308s
Прирост производительности впечатляет. Вы должны знать, что такие языки, как C# и Java с собственным GC, были представлены очень рано.string builder
, Golang был представлен только в 1.10, сроки не слишком ранние, но огромное улучшение не разочаровало. Давайте посмотрим, как это делает стандартная библиотека.
Разбор принципа strings.Builder
strings.Builder
Реализация в файлеstrings/builder.goВсего всего 120 строк, что очень изыскано. Выдержки ключевых кодов следующие:
type Builder struct {
addr *Builder // of receiver, to detect copies by value
buf []byte // 1
}
// Write appends the contents of p to b's buffer.
// Write always returns len(p), nil.
func (b *Builder) Write(p []byte) (int, error) {
b.copyCheck()
b.buf = append(b.buf, p...) // 2
return len(p), nil
}
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf)) // 3
}
func (b *Builder) copyCheck() {
if b.addr == nil {
// 4
// This hack works around a failing of Go's escape analysis
// that was causing b to escape and be heap allocated.
// See issue 23382.
// TODO: once issue 7921 is fixed, this should be reverted to
// just "b.addr = b".
b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
} else if b.addr != b {
panic("strings: illegal use of non-zero Builder copied by value")
}
}
- и
byte.Buffer
Идея аналогична, так как в процессе строительства струна будет постоянно разрушаться и перестраиваться, постарайтесь избежать этой проблемы и используйтеbuf []byte
для хранения содержимого строки. - Для операций записи просто записывайте байты в buf.
- чтобы решить
bytes.Buffer.String()
существующий[]byte -> string
И проблемы преобразования типа копирования памяти Вотunsafe.Pointer
Работа преобразования указателя хранения, реализует прямое преобразованиеbuf []byte
Преобразуйте в строковый тип, избегая проблемы полного выделения памяти. - Если мы реализуем strings.Builder сами, в большинстве случаев мы делаем первые 3 шага. Но стандартная библиотека идет еще дальше. Мы знаем, что стек Golang в большинстве случаев не требует от разработчиков внимания, и если работа, которую можно выполнить в стеке, уйдет в кучу, производительность сильно снизится. следовательно,
copyCheck
Добавлена строка сравнения, чтобы избежать ухода buf в кучу. В этой части вы можете прочитать больше о Дейве Чейни.Скрытые #прагмы Go.
останавливаться на достигнутом?
Как правило, методы, используемые в стандартной библиотеке Golang, будут постепенно продвигаться и стать лучшими практиками в определенных сценариях.
используется здесь*(*string)(unsafe.Pointer(&b.buf))
На самом деле, его можно использовать и в других сценариях. Например: как сравнитьstring
и[]byte
Это равно без выделения памяти?Кажется, что предзнаменование слишком очевидное, его должен написать каждый, только дайте код напрямую:
func unsafeEqual(a string, b []byte) bool {
bbp := *(*string)(unsafe.Pointer(&b))
return a == bbp
}