วิธีการเชื่อมสตริงอย่างมีประสิทธิภาพในระหว่างการเดินทาง


727

ใน Go stringเป็นชนิดดั้งเดิมซึ่งหมายความว่ามันเป็นแบบอ่านอย่างเดียวและการจัดการทุกอย่างจะสร้างสตริงใหม่

ดังนั้นหากฉันต้องการเชื่อมสตริงหลาย ๆ ครั้งโดยไม่ทราบความยาวของสตริงผลลัพธ์สิ่งที่ดีที่สุดในการทำคืออะไร

วิธีที่ไร้เดียงสาจะเป็น:

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

แต่ดูเหมือนจะไม่ได้มีประสิทธิภาพมาก



1
หมายเหตุ: คำถามนี้และคำตอบส่วนใหญ่ดูเหมือนจะเขียนก่อนที่จะappend()มาเป็นภาษาซึ่งเป็นทางออกที่ดีสำหรับเรื่องนี้ มันจะทำงานได้รวดเร็วเหมือนcopy()แต่จะเพิ่มขนาดชิ้นแรกแม้ว่าจะหมายถึงการจัดสรรอาเรย์สำรองใหม่หากความจุไม่เพียงพอ bytes.Bufferยังคงสมเหตุสมผลถ้าคุณต้องการวิธีการอำนวยความสะดวกเพิ่มเติมหรือหากแพ็คเกจที่คุณคาดว่าจะใช้
thomasrutter

7
ไม่เพียง "ดูเหมือนไม่มีประสิทธิภาพมาก"; มีปัญหาเฉพาะที่การจ้างงานที่ไม่ใช่ CS ใหม่ทุกครั้งที่เราได้รับในช่วงสองสามสัปดาห์แรกของงาน มันเป็นกำลังสอง - O (n * n) 1 + 2 + 3 + 4 + ...คิดว่าลำดับหมายเลข: มันเป็นพื้นที่ของรูปสามเหลี่ยมฐานที่n*(n+1)/2 nคุณจัดสรรขนาด 1 แล้วขนาด 2 จากนั้นขนาด 3 ฯลฯ เมื่อคุณต่อท้ายสตริงที่ไม่เปลี่ยนรูปในวง การใช้ทรัพยากรกำลังสองนี้แสดงให้เห็นตัวมันเองในหลาย ๆ ทางมากกว่าแค่นี้
Rob

คำตอบ:


856

วิธีการใหม่:

จาก 1.10 ไปมีstrings.Builderประเภทโปรดดูที่คำตอบนี้เพื่อดูรายละเอียดเพิ่มเติมโปรดดูที่คำตอบนี้สำหรับรายละเอียดเพิ่มเติม

วิธีเก่า:

ใช้bytesแพ็คเกจ แต่ก็มีประเภทซึ่งการดำเนินการBufferio.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 ())
รูปที่

26
แทนการที่คุณสามารถทำได้buffer := bytes.NewBufferString("") var buffer bytes.Bufferคุณไม่จำเป็นต้องใช้เครื่องหมายอัฒภาคเลย :)
crazy2be

66
เร็วอย่างไม่น่าเชื่อ ทำบางอย่างไร้เดียงสา "+" สตริง concat ในโปรแกรมการเดินทางของฉันจาก 3 นาที 1.3 วินาที
Malcolm

10
+1 สำหรับ "O (n) เวลา"; ฉันคิดว่ามันสำคัญที่จะพูดเพิ่มเติมเช่นนี้
ขัดแย้งกับ

8
ไป 1.10 เพิ่มstrings.Builderซึ่งเป็นเหมือน bytes.Buffer แต่เร็วขึ้นเมื่อเป้าหมายสุดท้ายของคุณเป็นสตริง
Josh Bleecher Snyder

272

วิธีที่มีประสิทธิภาพมากที่สุดในการสตริง concatenate copyคือการใช้ฟังก์ชั่นในตัว ในการทดสอบของฉันวิธีการที่เป็น ~ 3x เร็วกว่าการใช้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
bytes.Buffer ควรทำเช่นเดียวกันกับสำเนา (ด้วยการทำบัญชีพิเศษบางอย่างที่ฉันเดา) และความเร็วไม่แตกต่างกัน ดังนั้นฉันจะใช้มัน :) ความแตกต่างที่บัฟเฟอร์เริ่มต้นด้วย 0 ไบต์ดังนั้นจึงต้องทำการจัดสรรใหม่ (ซึ่งทำให้ดูเหมือนว่าช้าลงเล็กน้อยที่ฉันเดา) ใช้งานง่ายกว่า
Aktau

5
buffer.Write(ไบต์) เป็น 30% buffer.WriteStringเร็วกว่า [มีประโยชน์หากคุณสามารถรับข้อมูลได้[]byte]
Dani-Br

34
โปรดทราบว่าผลลัพธ์ของการวัดประสิทธิภาพนั้นผิดเพี้ยนและไม่ใช่ของแท้ ฟังก์ชั่นมาตรฐานที่แตกต่างกันจะถูกเรียกด้วยค่าที่แตกต่างกันb.Nดังนั้นคุณจึงไม่ได้เปรียบเทียบเวลาดำเนินการของงานเดียวกันที่จะดำเนินการ (เช่นฟังก์ชั่นหนึ่งอาจต่อท้าย1,000สตริงอีกอันหนึ่งอาจต่อท้าย10,000ซึ่งอาจทำให้ค่าเฉลี่ย เวลาของ 1 ผนวกในBenchmarkConcat()ตัวอย่าง) คุณควรใช้การต่อท้ายแบบเดียวกันในแต่ละกรณี (ไม่ใช่อย่างแน่นอนb.N) และทำการต่อข้อมูลทั้งหมดภายในเนื้อความของค่า( forถึงb.N2 forห่วงที่ฝังอยู่)
icza

18
นอกจากนี้การคัดลอกเบนช์มาร์กโดยการละเว้นเวลาที่ใช้ในการจัดสรรซึ่งรวมอยู่ในการวัดประสิทธิภาพอื่นอย่างชัดเจน
gha.st

6
นอกจากนี้เกณฑ์การคัดลอกยังขึ้นอยู่กับการทราบความยาวของสตริงผลลัพธ์
Skarllot

227

ไปใน 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 จะถูกนำมาใช้กับอินเทอร์เฟซที่มีอยู่ในใจ เพื่อให้คุณสามารถเปลี่ยนเป็นชนิดของตัวสร้างใหม่ได้ง่ายในรหัสของคุณ


ความแตกต่างจาก bytes.Buffer

  • มันสามารถเติบโตหรือรีเซ็ต

  • มันมีกลไก copyCheck ในตัวที่ป้องกันการคัดลอกโดยไม่ได้ตั้งใจ:

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

  • ในหนึ่งสามารถเข้าถึงไบต์พื้นฐานเช่นนี้bytes.Buffer(*Buffer).Bytes()

    • strings.Builder ป้องกันปัญหานี้
    • บางครั้งนี่ไม่ใช่ปัญหาและต้องการแทน
    • ตัวอย่างเช่น: สำหรับพฤติกรรมแอบดูเมื่อไบต์จะถูกส่งผ่านไปยังio.Readerฯลฯ

ตรวจสอบรหัสแหล่งที่มาของรายละเอียดเพิ่มเติมที่นี่


5
คุณหมายถึงอะไรโดย 'หลบหนี' คุณหมายถึงการหลีกเลี่ยงในสตริงหรือเพียงแค่ว่าไบต์พื้นฐานสามารถสัมผัส?
makhdumi

1
@makhdumi ใช่ที่ 2 การสัมผัสของไบต์พื้นฐาน
Inanc Gumus

มูลค่า noting strings.Builderดำเนินการวิธีการใช้เครื่องรับตัวชี้ซึ่งโยนฉันสักครู่ newเป็นผลให้ผมก็อาจจะสร้างได้โดยใช้
Duncan Jones

@DuncanJones ฉันได้เพิ่มบันทึกย่อไปแล้วเนื่องจากส่วนใหญ่ใช้สำหรับการแคชข้อมูลเป็นเรื่องปกติที่จะใช้ตัวชี้เมื่อใช้งานร่วมกันใน funcs ฯลฯ ใน func เดียวกันคุณสามารถใช้เป็นตัวชี้ได้เช่นกัน
Inanc Gumus

130

มีฟังก์ชันไลบรารีในแพ็คเกจสตริงที่เรียกว่าJoin: http://golang.org/pkg/strings/#Join

ดูรหัสของการJoinแสดงวิธีการที่คล้ายกันเพื่อผนวกฟังก์ชั่น Kinopiko wrote: 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
ไม่ทำงานเมื่อคุณต้องวนซ้ำบางสิ่งที่ไม่ใช่สตริง []
Malcolm

42

ฉันเพียงแค่ benchmarked คำตอบด้านบนโพสต์ดังกล่าวข้างต้นในรหัสของตัวเอง (ใช้เวลาเดินเพียงต้นไม้ recursive) และผู้ประกอบการ 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,"")
}

5
ฉันเชื่อว่า OP มีความกังวลเกี่ยวกับความซับซ้อนของหน่วยความจำมากกว่าความซับซ้อนของรันไทม์เนื่องจากข้อเท็จจริงที่ว่าการต่อสตริงที่ไร้เดียงสาส่งผลให้มีการจัดสรรหน่วยความจำใหม่ทุกครั้ง
galaktor

15
ความเร็วช้าของสิ่งนี้อาจเกี่ยวข้องกับการใช้ fmt.Fprint แทน buffer.WriteString("\t"); buffer.WriteString(subs[i]);
Robert Jack Will

ฉันดีใจที่รู้ว่าวิธีการที่ฉันชอบในการ(strings.Join)วิ่งเร็วที่สุดจากคำกล่าวนี้ซึ่ง(bytes.Buffer)เป็นผู้ชนะ!
Chetabahana

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 ( )ส่วนใหญ่ของไบต์เพื่อแปลงเป็นสตริงอีกครั้ง


มันน่าสนใจที่มีหลายวิธีที่จะทำสิ่งนี้ในโก
Yitzhak

11
ในการไปอย่างมีประสิทธิภาพก็ยังบอกว่าความคิดนั้นมีประโยชน์มากมันถูกจับในตัว ดังนั้นคุณสามารถแทนที่ฟังก์ชั่นของคุณด้วยappend(slice, byte...)ดูเหมือนว่า
Aktau

23

นี่เป็นวิธีที่เร็วที่สุดที่คุณไม่จำเป็นต้องรู้หรือคำนวณขนาดบัฟเฟอร์โดยรวมก่อน:

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

โดยมาตรฐานของฉันมันช้ากว่าโซลูชันคัดลอก 20% (8.1ns ต่อผนวกมากกว่า 6.72ns) แต่ยังเร็วกว่า 55% เมื่อใช้ bytes.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
ยินดีต้อนรับสู่ Stack Overflow! สละเวลาสักครู่เพื่ออ่านความช่วยเหลือการแก้ไขในศูนย์ช่วยเหลือ การจัดรูปแบบใน Stack Overflow นั้นแตกต่างจากไซต์อื่น ๆ
Rizier123

2
ในขณะที่ข้อมูลโค้ดนี้อาจแก้ไขคำถามรวมถึงคำอธิบายช่วยปรับปรุงคุณภาพของโพสต์ของคุณ จำไว้ว่าคุณกำลังตอบคำถามสำหรับผู้อ่านในอนาคตและคนเหล่านั้นอาจไม่ทราบสาเหตุของการแนะนำรหัสของคุณ โปรดอย่าพยายามทำให้รหัสของคุณแน่นเกินไปด้วยคำอธิบายที่อธิบายซึ่งจะช่วยลดความสามารถในการอ่านของทั้งรหัสและคำอธิบาย!
Rizier123

วิธีแก้ปัญหาง่ายๆ👍
ฟินน์

22

เพิ่มหมายเหตุในปี 2018

จากไป 1.10 มีstrings.Builderชนิดโปรดดูที่คำตอบนี้สำหรับรายละเอียดเพิ่มเติม

คำตอบก่อนหน้า 201x

รหัสมาตรฐานของ @ cd1 และคำตอบอื่น ๆ นั้นไม่ถูกต้อง b.Nไม่ควรตั้งค่าในฟังก์ชั่นมาตรฐาน มันถูกกำหนดโดยเครื่องมือทดสอบ go แบบไดนามิกเพื่อตรวจสอบว่าเวลาดำเนินการทดสอบมีเสถียรภาพ

ฟังก์ชั่นมาตรฐานควรใช้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ใช้หน่วยความจำและ allocs มากขึ้นฉันคิดว่ามันเกี่ยวข้องกับอัลกอริธึมการเติบโตที่ใช้ มันไม่ได้เพิ่มหน่วยความจำเร็วเท่าไบต์

คำแนะนำ:

  1. สำหรับงานง่ายๆเช่นสิ่งที่ OP ต้องการฉันจะใช้AppendหรือAppendPreAllocateหรือมันเร็วพอและใช้งานง่าย
  2. หากจำเป็นต้องอ่านและเขียนบัฟเฟอร์พร้อมกันให้ใช้bytes.Bufferแน่นอน นั่นคือสิ่งที่มันถูกออกแบบมาสำหรับ

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) หมายความว่าไม่ได้สร้างขึ้นมา มันอยู่ในห้องสมุดมาตรฐาน
Malcolm

ช้าเพียงเพราะใช้การสะท้อนกับข้อโต้แย้ง มันมีประสิทธิภาพ มิฉะนั้นจะมีประสิทธิภาพไม่น้อยไปกว่าการเข้าร่วมกับ
สตริง Join

11

ขยายคำตอบของ cd1: คุณอาจใช้ผนวก () แทนการคัดลอก () ผนวก () ทำให้การกำหนดล่วงหน้าล่วงหน้ามีขนาดใหญ่ขึ้นทำให้มีหน่วยความจำเพิ่มขึ้นเล็กน้อย แต่ประหยัดเวลา ฉันเพิ่มการวัดประสิทธิภาพอีกสองรายการที่ด้านบนของคุณ เรียกใช้ในเครื่องด้วย

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ครั้งเร็วกว่า concatenation สตริงโดยตรงผ่านทาง+ผู้ประกอบการ

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เป็นตัวแปรสาธารณะ
Vitaly Isaev

1
bN ไม่ควรถูกตั้งค่าในฟังก์ชันมาตรฐาน มันถูกกำหนดโดยเครื่องมือทดสอบการเดินทางแบบไดนามิก ฟังก์ชันมาตรฐานควรรันการทดสอบ bN เท่ากัน แต่ในรหัสของคุณ (รวมถึง @ cd1 's code) การทดสอบทุกครั้งในลูปจะเป็นการทดสอบที่แตกต่างกัน (เพราะความยาวของสตริงเติบโตขึ้น)
PickBoy

@PickBoy หากคุณปล่อยให้ชุดเครื่องมือทดสอบเป็นb.Nแบบไดนามิกคุณจะต้องจบด้วยสตริงที่มีความยาวต่างกันในกรณีทดสอบต่างๆ ดูความคิดเห็น
Vitaly Isaev

นั่นเป็นเหตุผลที่คุณควรเพิ่มการวนซ้ำภายในของการวนซ้ำจำนวนคงที่เช่น 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
กรุณาอย่าโพสต์รหัสคำตอบเท่านั้น โปรดให้คำอธิบายว่าโค้ดนี้ทำอะไรได้บ้างและทำไมจึงเป็นวิธีแก้ปัญหา
Korashen

-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)แปลง เมื่อเปรียบเทียบกับโซลูชันอื่น ๆ ที่โพสต์คุณสามารถตั้งชื่อข้อดีเพียงข้อเดียวของโซลูชันของคุณได้หรือไม่
pts

-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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.