在Go中测试空字符串的最佳方法是什么?

对于测试非空字符串(在Go中),哪种方法是最好的(最常用的)?

if len(mystring) > 0 { }

或者:

if mystring != "" { }

还是别的什么?

457150 次浏览

这似乎是不成熟的微优化。编译器可以为这两种情况或至少为这两种情况生成相同的代码

if len(s) != 0 { ... }

而且

if s != "" { ... }

因为语义显然是相等的。

这两种样式都在Go的标准库中使用。

if len(s) > 0 { ... }

可以在strconv包中找到

if s != "" { ... }

可以在encoding/json包中找到

两者都是惯用的,而且都足够清楚。这更多的是个人品味和清晰度的问题。

Russ Cox在golang-nuts线程中写道:

使代码清晰 如果我要查看元素x,我通常写
len(s) > x,即使x == 0,但如果我关心
"是这个特定的字符串"我倾向于写s == ""< / p > 假设一个成熟的编译器会编译
是合理的 Len (s) == 0和s == ""变成相同的,高效的代码 …< / p >

使代码清晰。

正如Timmmm的回答中指出的那样,在这两种情况下,Go编译器会生成相同的代码。

检查长度是一个很好的答案,但您也可以解释一个“空”字符串,它也只是空白。严格来说不是空的,但如果你愿意检查:

package main


import (
"fmt"
"strings"
)


func main() {
stringOne := "merpflakes"
stringTwo := "   "
stringThree := ""


if len(strings.TrimSpace(stringOne)) == 0 {
fmt.Println("String is empty!")
}


if len(strings.TrimSpace(stringTwo)) == 0 {
fmt.Println("String two is empty!")
}


if len(stringTwo) == 0 {
fmt.Println("String two is still empty!")
}


if len(strings.TrimSpace(stringThree)) == 0 {
fmt.Println("String three is empty!")
}
}

假设空格和所有前导和后面的空格都应该被删除:

import "strings"
if len(strings.TrimSpace(s)) == 0 { ... }
< p >因为:< br > len("") // is 0 < br > len(" ") // one empty space is 1 < br > len(" ") // two empty spaces is 2 < / p >

使用下面这样的函数会更简洁,更不容易出错:

func empty(s string) bool {
return len(strings.TrimSpace(s)) == 0
}

这将比修整整个字符串的性能更好,因为您只需要检查至少一个存在的非空格字符

// Strempty checks whether string contains only whitespace or not
func Strempty(s string) bool {
if len(s) == 0 {
return true
}


r := []rune(s)
l := len(r)


for l > 0 {
l--
if !unicode.IsSpace(r[l]) {
return false
}
}


return true
}

到目前为止,Go编译器在这两种情况下生成相同的代码,所以这是一个品味问题。GCCGo确实会生成不同的代码,但几乎没有人使用它,所以我不担心这个问题。

https://godbolt.org/z/fib1x1

我认为最好的方法是与空白字符串进行比较

BenchmarkStringCheck1检查空字符串

BenchmarkStringCheck2检查len 0

我检查空字符串和非空字符串检查。您可以看到,使用空字符串进行检查更快。

BenchmarkStringCheck1-4     2000000000           0.29 ns/op        0 B/op          0 allocs/op
BenchmarkStringCheck1-4     2000000000           0.30 ns/op        0 B/op          0 allocs/op




BenchmarkStringCheck2-4     2000000000           0.30 ns/op        0 B/op          0 allocs/op
BenchmarkStringCheck2-4     2000000000           0.31 ns/op        0 B/op          0 allocs/op

代码

func BenchmarkStringCheck1(b *testing.B) {
s := "Hello"
b.ResetTimer()
for n := 0; n < b.N; n++ {
if s == "" {


}
}
}


func BenchmarkStringCheck2(b *testing.B) {
s := "Hello"
b.ResetTimer()
for n := 0; n < b.N; n++ {
if len(s) == 0 {


}
}
}

只是向评论中添加更多

主要是关于如何做性能测试。

我用以下代码进行了测试:

import (
"testing"
)


var ss = []string{"Hello", "", "bar", " ", "baz", "ewrqlosakdjhf12934c r39yfashk fjkashkfashds fsdakjh-", "", "123"}


func BenchmarkStringCheckEq(b *testing.B) {
c := 0
b.ResetTimer()
for n := 0; n < b.N; n++ {
for _, s := range ss {
if s == "" {
c++
}
}
}
t := 2 * b.N
if c != t {
b.Fatalf("did not catch empty strings: %d != %d", c, t)
}
}
func BenchmarkStringCheckLen(b *testing.B) {
c := 0
b.ResetTimer()
for n := 0; n < b.N; n++ {
for _, s := range ss {
if len(s) == 0 {
c++
}
}
}
t := 2 * b.N
if c != t {
b.Fatalf("did not catch empty strings: %d != %d", c, t)
}
}
func BenchmarkStringCheckLenGt(b *testing.B) {
c := 0
b.ResetTimer()
for n := 0; n < b.N; n++ {
for _, s := range ss {
if len(s) > 0 {
c++
}
}
}
t := 6 * b.N
if c != t {
b.Fatalf("did not catch empty strings: %d != %d", c, t)
}
}
func BenchmarkStringCheckNe(b *testing.B) {
c := 0
b.ResetTimer()
for n := 0; n < b.N; n++ {
for _, s := range ss {
if s != "" {
c++
}
}
}
t := 6 * b.N
if c != t {
b.Fatalf("did not catch empty strings: %d != %d", c, t)
}
}

结果是:

% for a in $(seq 50);do go test -run=^$ -bench=. --benchtime=1s ./...|grep Bench;done | tee -a log
% sort -k 3n log | head -10


BenchmarkStringCheckEq-4        150149937            8.06 ns/op
BenchmarkStringCheckLenGt-4     147926752            8.06 ns/op
BenchmarkStringCheckLenGt-4     148045771            8.06 ns/op
BenchmarkStringCheckNe-4        145506912            8.06 ns/op
BenchmarkStringCheckLen-4       145942450            8.07 ns/op
BenchmarkStringCheckEq-4        146990384            8.08 ns/op
BenchmarkStringCheckLenGt-4     149351529            8.08 ns/op
BenchmarkStringCheckNe-4        148212032            8.08 ns/op
BenchmarkStringCheckEq-4        145122193            8.09 ns/op
BenchmarkStringCheckEq-4        146277885            8.09 ns/op

有效变体通常不会达到最快时间,不同变体的最高速度之间只有最小的差异(约0.01ns/op)。

如果我看完整的日志,try之间的差异大于benchmark函数之间的差异。

而且两者之间似乎没有任何可测量的差异 BenchmarkStringCheckEq和BenchmarkStringCheckNe 或BenchmarkStringCheckLen和BenchmarkStringCheckLenGt 即使后面的变体应该inc c 6次而不是2次。

通过添加带有修改过的测试或内循环的测试,您可以尝试获得对相同性能的信心。这样更快:

func BenchmarkStringCheckNone4(b *testing.B) {
c := 0
b.ResetTimer()
for n := 0; n < b.N; n++ {
for _, _ = range ss {
c++
}
}
t := len(ss) * b.N
if c != t {
b.Fatalf("did not catch empty strings: %d != %d", c, t)
}
}

这并不是更快:

func BenchmarkStringCheckEq3(b *testing.B) {
ss2 := make([]string, len(ss))
prefix := "a"
for i, _ := range ss {
ss2[i] = prefix + ss[i]
}
c := 0
b.ResetTimer()
for n := 0; n < b.N; n++ {
for _, s := range ss2 {
if s == prefix {
c++
}
}
}
t := 2 * b.N
if c != t {
b.Fatalf("did not catch empty strings: %d != %d", c, t)
}
}

这两种变体通常比主要测试之间的差异更快或更慢。

使用具有相关分布的字符串生成器生成测试字符串(ss)也很好。长度也是可变的。

所以我对go中测试空字符串的主要方法之间的性能差异没有任何信心。

我可以有信心地说,不测试空字符串比测试空字符串更快。而且测试空字符串比测试一个字符字符串(前缀变体)更快。

根据官方指南,从性能的角度来看,它们看起来是等效的(ANisus回答), s != ""由于语法优势,将更好。如果变量不是字符串,S != ""将在编译时失败,而len(S) == 0将通过其他几种数据类型。

我认为== ""更快,更易读。

package main


import(
"fmt"
)
func main() {
n := 1
s:=""
if len(s)==0{
n=2
}
fmt.Println("%d", n)
}
dlv debug playground.go cmp with len(s) and ==""我知道了 S == ";情况< / p >
    playground.go:6         0x1008d9d20     810b40f9        MOVD 16(R28), R1
playground.go:6         0x1008d9d24     e28300d1        SUB $32, RSP, R2
playground.go:6         0x1008d9d28     5f0001eb        CMP R1, R2
playground.go:6         0x1008d9d2c     09070054        BLS 56(PC)
playground.go:6         0x1008d9d30*    fe0f16f8        MOVD.W R30, -160(RSP)


playground.go:6         0x1008d9d34     fd831ff8        MOVD R29, -8(RSP)
playground.go:6         0x1008d9d38     fd2300d1        SUB $8, RSP, R29
playground.go:7         0x1008d9d3c     e00340b2        ORR $1, ZR, R0

    playground.go:7         0x1008d9d40     e01f00f9        MOVD R0, 56(RSP)
playground.go:8         0x1008d9d44     ff7f05a9        STP (ZR, ZR), 80(RSP)

    playground.go:9         0x1008d9d48     01000014        JMP 1(PC)
playground.go:10        0x1008d9d4c     e0037fb2        ORR $2, ZR, R0

len (s) = = 0的情况

    playground.go:6         0x100761d20     810b40f9        MOVD 16(R28), R1
playground.go:6         0x100761d24     e2c300d1        SUB $48, RSP, R2
playground.go:6         0x100761d28     5f0001eb        CMP R1, R2
playground.go:6         0x100761d2c     29070054        BLS 57(PC)
playground.go:6         0x100761d30*    fe0f15f8        MOVD.W R30, -176(RSP)


playground.go:6         0x100761d34     fd831ff8        MOVD R29, -8(RSP)
playground.go:6         0x100761d38     fd2300d1        SUB $8, RSP, R29
playground.go:7         0x100761d3c     e00340b2        ORR $1, ZR, R0

    playground.go:7         0x100761d40     e02300f9        MOVD R0, 64(RSP)
playground.go:8         0x100761d44     ff7f06a9        STP (ZR, ZR), 96(RSP)
playground.go:9         0x100761d48     ff2700f9        MOVD ZR, 72(RSP)

    playground.go:9         0x100761d4c     01000014        JMP 1(PC)
playground.go:10        0x100761d50     e0037fb2        ORR $2, ZR, R0
playground.go:10        0x100761d54     e02300f9        MOVD R0, 64(RSP)
playground.go:10        0x100761d58     01000014        JMP 1(PC)
playground.go:6         0x104855d2c     09070054        BLS 56(PC)

引用