在这里,我试图从包含字符串的切片中为我的API创建查询字符串。
即。 where={"node_name":"node1","node_name":"node_2"}
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串联,您的解决方案使用了太多分配。
string
我们将创建一些替代的,更快的和/或更优雅的解决方案。请注意,以下解决方案不检查节点值是否包含引号"字符。如果愿意,则必须以某种方式对其进行转义(否则结果将是无效的查询字符串)。
"
完整的可运行代码可以在Go Playground上找到。完整的测试/基准测试代码也可以在Go Playground上找到,但是它不可运行,将它们保存到Go工作区(例如$GOPATH/src/query/query.go和$GOPATH/src/query/query_test.go)中,然后使用运行go test -bench .。
$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) }
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.Bufferusing 时传递了足够大的字节片,则仍可以将分配减少为1 bytes.NewBuffer()。我们可以事先计算所需的大小:
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() }
请注意,在size计算8是字符串的大小where={}和15是字符串的大小"node_name":"",。
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 -}} }`
strings.Join()
由于其简单性,该解决方案很有趣。我们可以使用适当的前缀和后缀之间strings.Join()的静态文本来连接节点","node_name":"。
","node_name":"
需要注意的重要事项:strings.Join()将内置copy()函数与单个预分配[]byte缓冲区一起使用,因此非常快! “作为一种特殊情况,它(该copy()函数)还将把字节从字符串复制到字节的一部分。”
copy()
[]byte
func buildJoin(nodes []string) string { if len(nodes) == 0 { return "where={}" } return `where={"node_name":"` + strings.Join(nodes, `","node_name":"`) + `"}` }
我们将使用以下nodes值进行基准测试:
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()是 3.6倍 的速度比buildOriginal(),和buildBuffer2()(同预先计算的大小)约 30% 的速度比buildBuffer(),因为它并不需要重新分配(并复制)的内部缓冲区。
buildBuffer()
buildOriginal()
buildBuffer2()
一些令人惊讶的事实:buildJoin()非常快,甚至击败buildBuffer2()由 2.4倍 (因为只使用到[]byte和copy())。buildTemplate()另一方面证明速度很慢:比慢 7倍buildOriginal()。这样做的主要原因是因为它在引擎盖下使用(必须使用)反射。
buildJoin()
buildTemplate()