使用切片值的Golang字符串格式 [英] Golang string format using slice values

查看:186
本文介绍了使用切片值的Golang字符串格式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这里我试图从包含字符串的片段为我的API创建一个查询字符串。

其中= {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 链接。



什么是获得结果的最好方法吗?
解决方案您的解决方案使用的方式太多,因为字符串连接。



我们将创建一些替代,更快和/或更优雅的解决方案。请注意,下面的解决方案不检查节点值是否包含引号字符,如果他们愿意的话,那些将不得不以某种方式转义(否则结果将是无效的查询字符串)。



完整的可运行代码可以在 Go Playground 。完整的测试/基准测试代码也可以在上找到Go Playground ,但它不能运行,保存到你的Go工作区(例如 $ GOPATH / src / query / query.go $ GOPATH / src / query / query_test.go ),然后运行 go test -bench。



另外一定要检查这个相关的问题:如何在Go中有效地连接字符串?



替代方案



G enesis



你的逻辑可以被下面的函数捕获:

$ $ $ $ $ $ $ $ $ $ $ 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 {
如果我> 0 {
buf.WriteByte(',')
}
buf.WriteString(`node_name:`)
buf.WriteString(v)
buf .Brite(''')

buf.WriteByte('}')
return buf.String()
}


使用它:

  nodes:= []字符串{node1,node2} 
fmt.Println(buildBuffer(nodes))



<输出:

 其中= {node_name:node1,node_name:node2} 



bytes.Buffer 改进



bytes.Buffer 仍然会做一些重新分配,尽管远远少于原来的解决方案。



但是,如果我们在使用 bytes.Buffer
时传递足够大的字节片段, //golang.org/pkg/bytes/#NewBufferrel =nofollow noreferrer> bytes.NewBuffer()

$ p $ func buildBuffer2(nodes [] string)string {
size:= 8我们可以计算所需的大小+ len(nodes)* 15
for _,v:= range nodes {
size + = len(v)
}
buf:= bytes.NewBuffer(make([ ]字节,0,大小))
buf.WriteString(where = {)
for i,v:=范围节点{
if i> 0 {
buf.WriteByte(',')
}
buf.WriteString(`node_name:`)
buf.WriteString(v)
buf .Brite(''')

buf.WriteByte('}')
return buf.String()
}
注意在 size 计算中 8



>是字符串的大小,其中= {} 15 是字符串 node_name:,



使用文本/模板



我们也可以创建一个文本模板,并使用 text / template 包执行它,高效地产生结果:

  var t = template.Must(template.New()。Parse(templ))
$ b 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)//处理错误
}
返回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() ,所以它非常快! 作为一个特殊情况,它( copy()函数)也会将字节从字符串复制到一个字节片段。 p>

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



$ h $基准测试结果

我们将以下面的节点为基准:

  var nodes = [节点2,节点3,节点2,节点3,节点2,节点2,节点2,节点3,节点4,节点2,节点3,节点4, ,nodethree,fourthNode,
n1,node2,nodethree,fourthNode,
n1,node2,nodethree,fourthNode b $ b}

基准代码如下所示:

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

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

// ...所有其他基准测试函数看起来都一样

现在的结果:
$ b $ pre $ 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()非常快,e因为只使用了一个 [] byte buildBuffer2() copy())。另一方面, buildTemplate()被证明是相当慢的:<7次比 buildOriginal()。主要原因是因为它使用了(必须使用)反射。


Here I am trying to create a query string for my API from a slice containing strings.

ie. 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)
}

Here is goplayground link.

What is the best way to get the result?

解决方案

Your solution uses way too many allocations due to string concatenations.

We'll create some alternative, faster and/or more elegant solutions. Note that the below solutions do not check if node values contain the quotation mark " character. If they would, those would have to be escaped somehow (else the result would be an invalid query string).

The complete, runnable code can be found on the Go Playground. The complete testing / benchmarking code can also be found on the Go Playground, but it is not runnable, save both to your Go workspace (e.g. $GOPATH/src/query/query.go and $GOPATH/src/query/query_test.go) and run it with go test -bench ..

Also be sure to check out this related question: How to efficiently concatenate strings in Go?

Alternatives

Genesis

Your logic can be captured by the following function:

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)
}

Using bytes.Buffer

Much better would be to use a single buffer, e.g. bytes.Buffer, build the query in that, and convert it to string at the end:

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()
}

Using it:

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

Output:

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

bytes.Buffer improved

bytes.Buffer will still do some reallocations, although much less than your original solution.

However, we can still reduce the allocations to 1, if we pass a big-enough byte slice when creating the bytes.Buffer using bytes.NewBuffer(). We can calculate the required size prior:

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()
}

Note that in size calculation 8 is the size of the string where={} and 15 is the size of the string "node_name":"",.

Using text/template

We can also create a text template, and use the text/template package to execute it, efficiently generating the result:

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 -}}
}`

Using strings.Join()

This solution is interesting due to its simplicity. We can use strings.Join() to join the nodes with the static text ","node_name":" in between, proper prefix and postfix applied.

An important thing to note: strings.Join() uses the builtin copy() function with a single preallocated []byte buffer, so it's very fast! "As a special case, it (the copy() function) also will copy bytes from a string to a slice of bytes."

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

Benchmark results

We'll benchmark with the following nodes value:

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

And the benchmarking code looks like this:

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

And now the results:

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

Some unsurprising facts: buildBuffer() is 3.6 times faster than buildOriginal(), and buildBuffer2() (with pre-calculated size) is about 30% faster than buildBuffer() because it does not need to reallocate (and copy over) the internal buffer.

Some surprising facts: buildJoin() is extremely fast, even beats buildBuffer2() by 2.4 times (due to only using a []byte and copy()). buildTemplate() on the other hand proved quite slow: 7 times slower than buildOriginal(). The main reason for this is because it uses (has to use) reflection under the hood.

这篇关于使用切片值的Golang字符串格式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆