Go에서 a string
는 기본 유형이므로 읽기 전용이므로 모든 조작시 새 문자열이 작성됩니다.
결과 문자열의 길이를 모른 채 문자열을 여러 번 연결하려면 가장 좋은 방법은 무엇입니까?
순진한 방법은 다음과 같습니다.
s := ""
for i := 0; i < 1000; i++ {
s += getShortStringFromSomewhere()
}
return s
그러나 그것은 매우 효율적으로 보이지 않습니다.
답변
새로운 길:
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) 시간 안에 이루어집니다.
답변
문자열을 연결하는 가장 효율적인 방법은 내장 함수를 사용하는 것 copy
입니다. 내 테스트에서 그 접근 방식은 bytes.Buffer
연산자를 사용하는 것보다 ~ 3 배 빠르며 연산자를 사용하는 것보다 훨씬 빠릅니다 (~ 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)
}
}
답변
Go 1.10+에는 strings.Builder
, here이 있습니다 .
Builder는 Write 메서드를 사용하여 문자열을 효율적으로 작성하는 데 사용됩니다. 메모리 복사를 최소화합니다. 0 값을 사용할 준비가되었습니다.
예
와 거의 동일합니다 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의 메소드는 기존 인터페이스를 염두에두고 구현되고 있습니다. 코드에서 쉽게 새 빌더 유형으로 전환 할 수 있습니다.
- Grow (int) -> bytes.Buffer # Grow
- Len () int- > bytes. 버퍼 #Len
- Reset () -> bytes.Buffer # Reset
- String () 문자열 -> fmt.Stringer
- 쓰기 ([] 바이트) (int, error) -> io.Writer
- WriteByte (byte) 오류 -> io.ByteWriter
- WriteRune (rune) (int, error) -> bufio.Writer # WriteRune – bytes.Buffer # WriteRune
- WriteString (string) (int, error) -> io.stringWriter
바이트와의 차이점
-
커지거나 재설정 만 할 수 있습니다.
-
여기에는 실수로 복사하는 것을 방지하는 copyCheck 메커니즘이 내장되어 있습니다.
func (b *Builder) copyCheck() { ... }
-
에서 다음
bytes.Buffer
과 같이 기본 바이트에 액세스 할 수 있습니다(*Buffer).Bytes()
.strings.Builder
이 문제를 방지합니다.- 때때로, 이것은 문제가되지 않고 대신에 바람직합니다.
- 예를 들면 : 바이트 등을 전달할 때의 엿보기 동작의 경우
io.Reader
.
자세한 내용은 소스 코드를 확인하십시오 ( 여기) .
답변
문자열 패키지에는 다음과 같은 라이브러리 함수가 있습니다 Join
.
http://golang.org/pkg/strings/#Join
코드를 Join
살펴보면 Kinopiko가 작성한 Append 함수에 대한 비슷한 접근 방식이 표시됩니다. 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
답변
방금 내 코드 (재귀 트리 워크)에서 위에 게시 된 최상위 답변을 벤치마킹했으며 간단한 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,"")
}
답변
큰 바이트 조각을 만들고 문자열 조각을 사용하여 짧은 문자열의 바이트를 복사 할 수 있습니다. “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 ( )
큰 바이트 조각을 사용 하여 문자열로 다시 변환하십시오.
답변
전체 버퍼 크기를 먼저 알거나 계산할 필요가없는 가장 빠른 솔루션입니다.
var data []byte
for i := 0; i < 1000; i++ {
data = append(data, getShortStringFromSomewhere()...)
}
return string(data)
내 벤치 마크 에 따르면 복사 솔루션보다 20 % 느리지 만 (6.72ns가 아닌 추가 당 8.1ns) bytes.Buffer를 사용하는 것보다 여전히 55 % 빠릅니다.