簡體   English   中英

使用切片值的Golang字符串格式

[英]Golang string format using slice values

在這里,我嘗試從包含字符串的切片為我的API創建查詢字符串。

即。 where={"node_name":"node1","node_name":"node_2"}

import (
   "fmt"
   "strings"
)

func main() {
    nodes := []string{"node1", "node2"}
    var query string
    for _, n := range nodes {
        query += fmt.Sprintf("\"node_name\":\"%s\",", n)
    }
    query = strings.TrimRight(query, ",")
    final := fmt.Sprintf("where={%s}", query)
    fmt.Println(final)
}

這是goplayground鏈接。

獲得結果的最佳方法是什么?

由於string連接,您的解決方案使用了太多的分配。

我們將創建一些替代的,更快的和/或更優雅的解決方案。 請注意,下面的解決方案不會檢查節點值是否包含引號" character。如果願意,那些必須以某種方式進行轉義(否則結果將是無效的查詢字符串)。

完整的可運行代碼可以在Go Playground上找到。 完整的測試/基准測試代碼也可以在Go Playground上找到,但它不可運行,同時保存到Go工作區(例如$GOPATH/src/query/query.go$GOPATH/src/query/query_test.go並使用go test -bench .運行它go test -bench .

另外一定要看看這個相關的問題: 如何在Go中有效地連接字符串?

備擇方案

創世紀

您的邏輯可以通過以下函數捕獲:

func buildOriginal(nodes []string) string {
    var query string
    for _, n := range nodes {
        query += fmt.Sprintf("\"node_name\":\"%s\",", n)
    }
    query = strings.TrimRight(query, ",")
    return fmt.Sprintf("where={%s}", query)
}

使用bytes.Buffer

更好的方法是使用單個緩沖區,例如bytes.Buffer ,在其中構建查詢,並在string處將其轉換為string

func buildBuffer(nodes []string) string {
    buf := &bytes.Buffer{}
    buf.WriteString("where={")
    for i, v := range nodes {
        if i > 0 {
            buf.WriteByte(',')
        }
        buf.WriteString(`"node_name":"`)
        buf.WriteString(v)
        buf.WriteByte('"')
    }
    buf.WriteByte('}')
    return buf.String()
}

使用它:

nodes := []string{"node1", "node2"}
fmt.Println(buildBuffer(nodes))

輸出:

where={"node_name":"node1","node_name":"node2"}

bytes.Buffer改進了

bytes.Buffer仍會進行一些重新分配,盡管比原始解決方案要少得多。

但是,如果我們在使用bytes.NewBuffer()創建bytes.Buffer時傳遞足夠大的字節切片,我們仍然可以將分配減少到1。 我們可以先計算所需的尺寸:

func buildBuffer2(nodes []string) string {
    size := 8 + len(nodes)*15
    for _, v := range nodes {
        size += len(v)
    }
    buf := bytes.NewBuffer(make([]byte, 0, size))
    buf.WriteString("where={")
    for i, v := range nodes {
        if i > 0 {
            buf.WriteByte(',')
        }
        buf.WriteString(`"node_name":"`)
        buf.WriteString(v)
        buf.WriteByte('"')
    }
    buf.WriteByte('}')
    return buf.String()
}

請注意,在size計算中, 8是字符串的大小, where={}15是字符串"node_name":"",的大小"node_name":"",

使用text/template

我們還可以創建一個文本模板,並使用text/template包來執行它,有效地生成結果:

var t = template.Must(template.New("").Parse(templ))

func buildTemplate(nodes []string) string {
    size := 8 + len(nodes)*15
    for _, v := range nodes {
        size += len(v)
    }
    buf := bytes.NewBuffer(make([]byte, 0, size))
    if err := t.Execute(buf, nodes); err != nil {
        log.Fatal(err) // Handle error
    }
    return buf.String()
}

const templ = `where={
{{- range $idx, $n := . -}}
    {{if ne $idx 0}},{{end}}"node_name":"{{$n}}"
{{- end -}}
}`

使用strings.Join()

由於其簡單性,該解決方案很有趣。 我們可以使用strings.Join()來加入節點與靜態文本","node_name":"之間,正確的前綴和后綴應用。

需要注意的一件重要事情: strings.Join()使用內置的copy()函數和一個預先分配的[]byte緩沖區,所以它非常快! “作為一種特殊情況,它( copy()函數)也會將字符串中的字節復制到一片字節。”

func buildJoin(nodes []string) string {
    if len(nodes) == 0 {
        return "where={}"
    }
    return `where={"node_name":"` + strings.Join(nodes, `","node_name":"`) + `"}`
}

基准測試結果

我們將使用以下nodes值進行基准測試:

var nodes = []string{"n1", "node2", "nodethree", "fourthNode",
    "n1", "node2", "nodethree", "fourthNode",
    "n1", "node2", "nodethree", "fourthNode",
    "n1", "node2", "nodethree", "fourthNode",
    "n1", "node2", "nodethree", "fourthNode",
}

基准測試代碼如下所示:

func BenchmarkOriginal(b *testing.B) {
    for i := 0; i < b.N; i++ {
        buildOriginal(nodes)
    }
}

func BenchmarkBuffer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        buildBuffer(nodes)
    }
}

// ... All the other benchmarking functions look the same

現在的結果是:

BenchmarkOriginal-4               200000             10572 ns/op
BenchmarkBuffer-4                 500000              2914 ns/op
BenchmarkBuffer2-4               1000000              2024 ns/op
BenchmarkBufferTemplate-4          30000             77634 ns/op
BenchmarkJoin-4                  2000000               830 ns/op

一些不足為奇的事實: buildBuffer()buildOriginal()3.6倍 ,而buildBuffer2() (預先計算的大小)比buildBuffer()30%,因為它不需要重新分配(並復制)內部緩沖。

一些令人驚訝的事實: buildJoin()非常快,甚至比buildBuffer2()2.4倍 (由於只使用了[]bytecopy() )。 buildTemplate()證明非常慢:比buildOriginal()7倍 造成這種情況的主要原因是它在引擎蓋下使用(必須使用)反射。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM