string - 使用 slice 值的 Golang 字符串格式

标签 string go format slice

在这里,我试图从包含字符串的 slice 中为我的 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 连接,您的解决方案使用了太多分配。

我们将创建一些替代的、更快和/或更优雅的解决方案。请注意,以下解决方案不会检查节点值是否包含引号 " 字符。如果包含,则必须以某种方式对这些字符进行转义(否则结果将是无效的查询字符串)。

完整的可运行代码可以在 Go Playground 上找到.完整的测试/基准测试代码也可以在 Go Playground 上找到。 ,但它不可运行,将两者都保存到您的 Go 工作区(例如 $GOPATH/src/query/query.go$GOPATH/src/query/query_test.go ) 并使用 go test -bench . 运行它。

另请务必查看此相关问题:How to efficiently concatenate strings in 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:

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 时传递足够大的 byte slice ,我们仍然可以将分配减少到 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":"", 的大小。

使用文本/模板

我们还可以创建一个文本模板,并使用 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() 函数)还将字节从字符串复制到 byte slice 。”

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()buildBuffer2()3.6 倍>(具有预先计算的大小)比 buildBuffer() 快大约 30%,因为它不需要重新分配(和复制)内部缓冲区。

一些令人惊讶的事实:buildJoin() 非常快,甚至比 buildBuffer2()2.4 倍(因为只使用了一个 []bytecopy())。另一方面,buildTemplate() 被证明相当慢:比 buildOriginal()7 倍。这样做的主要原因是因为它在底层使用(必须使用)反射。

关于string - 使用 slice 值的 Golang 字符串格式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41457273/

相关文章:

go - http.Post 无法正常工作?

reflection - 按名称调用其他包中的方法?

Go 中的继承

java - 在 Java 中解析非平面文件格式的最佳方法是什么?

java - 在 Java 中如何检查我是否已到达 String 的末尾?

r - 当条目本身的值为零且条目之前的条目为 0 时,跳过 Paste0 条目

python - 如何在python中显示没有秒的区域设置敏感时间格式

Android软键盘首先显示数字 View

c - 将指针传递给 char * 时 realloc() 失败。为什么? (因为它是按值计算的,而不是引用!)

c - 在新字符串中仅打印一次重叠字符(字符串组合)