Jam*_*pam 5 string format go 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)
}
Run Code Online (Sandbox Code Playgroud)
这是goplayground链接.
获得结果的最佳方法是什么?
icz*_*cza 12
由于string连接,您的解决方案使用了太多的分配.
我们将创建一些替代的,更快的和/或更优雅的解决方案.请注意,以下解决方案不检查节点值是否包含引号"字符.如果他们愿意,那些必须以某种方式进行转义(否则结果将是无效的查询字符串).
完整的可运行代码可以在Go Playground上找到.完整的测试/基准测试代码也可以在Go Playground上找到,但它不可运行,只保存到Go工作区(例如$GOPATH/src/query/query.go和$GOPATH/src/query/query_test.go)并运行它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)
}
Run Code Online (Sandbox Code Playgroud)
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()
}
Run Code Online (Sandbox Code Playgroud)
使用它:
nodes := []string{"node1", "node2"}
fmt.Println(buildBuffer(nodes))
Run Code Online (Sandbox Code Playgroud)
输出:
where={"node_name":"node1","node_name":"node2"}
Run Code Online (Sandbox Code Playgroud)
bytes.Buffer 改善bytes.Buffer 仍然会进行一些重新分配,尽管比原来的解决方案要少得多.
但是,如果我们在创建bytes.Buffer使用时传递足够大的字节切片,我们仍然可以将分配减少到1 bytes.NewBuffer().我们可以先计算所需的尺寸:
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()
}
Run Code Online (Sandbox Code Playgroud)
请注意,在size计算中8是字符串的大小,where={}并且15是字符串的大小"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 -}}
}`
Run Code Online (Sandbox Code Playgroud)
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":"`) + `"}`
}
Run Code Online (Sandbox Code Playgroud)
我们将使用以下nodes值进行基准测试:
var nodes = []string{"n1", "node2", "nodethree", "fourthNode",
"n1", "node2", "nodethree", "fourthNode",
"n1", "node2", "nodethree", "fourthNode",
"n1", "node2", "nodethree", "fourthNode",
"n1", "node2", "nodethree", "fourthNode",
}
Run Code Online (Sandbox Code Playgroud)
基准测试代码如下所示:
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
Run Code Online (Sandbox Code Playgroud)
现在的结果是:
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
Run Code Online (Sandbox Code Playgroud)
有些令人吃惊的事实:buildBuffer()是3.6倍的速度比buildOriginal(),和buildBuffer2()(同预先计算的大小)约30%的速度比buildBuffer(),因为它并不需要重新分配(并复制)的内部缓冲区.
一些令人惊讶的事实:buildJoin()非常快,甚至击败buildBuffer2()由2.4倍(因为只使用到[]byte和copy()).buildTemplate()从另一方面证明相当缓慢:7次慢buildOriginal().造成这种情况的主要原因是它在引擎盖下使用(必须使用)反射.