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

时间:2013-09-03 14:02:36

标签: string go is-empty

对于测试非空字符串(在Go中),哪种方法最好(更多是idomatic)?

if len(mystring) > 0 { }

或者:

if mystring != "" { }

还是其他什么?

10 个答案:

答案 0 :(得分:283)

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

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

可以在strconv包中找到:http://golang.org/src/pkg/strconv/atoi.go

if s != "" { ... }

可以在encoding/json包中找到:http://golang.org/src/pkg/encoding/json/encode.go

两者都是惯用的并且足够清晰。这更多的是个人品味和清晰度。

Russ Cox用golang-nuts thread写道:

  

使代码清晰的那个   如果我要查看元素x,我通常会写   len(s)> x,即使是x == 0,但如果我关心的话   “这是这个特定的字符串”我倾向于写s ==“”。

     

假设成熟的编译器将编译
是合理的   len(s)== 0和s ==“”进入相同,有效的代码   现在6g等将s ==“”编译成函数调用
  虽然len(s)== 0不是,但那已经在我的待办事项清单上修复了。

     

使代码清晰。

答案 1 :(得分:25)

这似乎是过早的微观优化。编译器可以自由地为这两种情况生成相同的代码,或者至少为这两种情况生成相同的代码

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

if s != "" { ... }

因为语义明显相等。

答案 2 :(得分:16)

检查长度是一个很好的答案,但你也可以考虑一个空的"字符串也只是空格。不是"技术上"空的,但如果你想检查:

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

答案 3 :(得分:8)

假设应删除空格和所有前导和尾随空格:

import "strings"
if len(strings.TrimSpace(s)) == 0 { ... }

因为:
len("") // is 0
len(" ") // one empty space is 1
len(" ") // two empty spaces is 2

答案 4 :(得分:4)

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

答案 5 :(得分:1)

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

https://godbolt.org/z/fib1x1

答案 6 :(得分:0)

使用如下所示的功能会更清晰,更不容易出错:

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

答案 7 :(得分: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
}

答案 8 :(得分:0)

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

BenchmarkStringCheck1正在检查空白字符串

BenchmarkStringCheck2正在检查len零

我检查了空字符串和非空字符串。您可以看到用空字符串检查更快。

<GridLayout>
    <ScrollView class="page">
        <StackLayout class="home-panel">
            <GridLayout rows="*">
                <ListView [items]="peripherals">
                    <ng-template let-peripheral="item" let-i="index" let-odd="odd"
                        let-even="even">
                        <StackLayout orientation="horizontal" class="list-group-item">
                            <Label [text]="peripheral.name"></Label>
                            <Label [text]="peripheral.UUID"></Label>
                        </StackLayout>
                    </ng-template>
                </ListView>
            </GridLayout>
        </StackLayout>
    </ScrollView>
</GridLayout>

代码

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

答案 9 :(得分:0)

只需向comment添加更多内容

主要关于如何进行性能测试。

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

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

如果我查看完整的日志,则尝试之间的差异大于基准函数之间的差异。

而且之间似乎也没有可测量的差异 BenchmarkStringCheckEq和BenchmarkStringCheckNe 或BenchmarkStringCheckLen和BenchmarkStringCheckLenGt 即使后面的变体应增加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中测试空字符串的主要方法之间的性能差异没有信心。

我可以放心地说,根本不测试空字符串要比测试空字符串更快。而且测试空字符串要比测试1个字符字符串(前缀变体)更快。