What is the best way to test for an empty string in Go?

前端 未结 10 658
终归单人心
终归单人心 2021-01-29 20:26

Which method is best (more idomatic) for testing non-empty strings (in Go)?

if len(mystring) > 0 { }

Or:

if mystring != \"\"         


        
相关标签:
10条回答
  • 2021-01-29 20:46

    Just to add more to comment

    Mainly about how to do performance testing.

    I did testing with following code:

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

    And results were:

    % 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
    

    Effectively variants usually do not reach fastest time and there is only minimal difference (about 0.01ns/op) between variant top speed.

    And if I look full log, difference between tries is greater than difference between benchmark functions.

    Also there does not seem to be any measurable difference between BenchmarkStringCheckEq and BenchmarkStringCheckNe or BenchmarkStringCheckLen and BenchmarkStringCheckLenGt even if latter variants should inc c 6 times instead of 2 times.

    You can try to get some confidence about equal performance by adding tests with modified test or inner loop. This is faster:

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

    This is not faster:

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

    Both variants are usually faster or slower than difference between main tests.

    It would also good to generate test strings (ss) using string generator with relevant distribution. And have variable lengths too.

    So I don't have any confidence of performance difference between main methods to test empty string in go.

    And I can state with some confidence, it is faster not to test empty string at all than test empty string. And also it is faster to test empty string than to test 1 char string (prefix variant).

    0 讨论(0)
  • 2021-01-29 20:48

    Checking for length is a good answer, but you could also account for an "empty" string that is also only whitespace. Not "technically" empty, but if you care to check:

    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!")
      }
    }
    
    0 讨论(0)
  • 2021-01-29 20:49

    Both styles are used within the Go's standard libraries.

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

    can be found in the strconv package: http://golang.org/src/pkg/strconv/atoi.go

    if s != "" { ... }
    

    can be found in the encoding/json package: http://golang.org/src/pkg/encoding/json/encode.go

    Both are idiomatic and are clear enough. It is more a matter of personal taste and about clarity.

    Russ Cox writes in a golang-nuts thread:

    The one that makes the code clear.
    If I'm about to look at element x I typically write
    len(s) > x, even for x == 0, but if I care about
    "is it this specific string" I tend to write s == "".

    It's reasonable to assume that a mature compiler will compile
    len(s) == 0 and s == "" into the same, efficient code.
    ...

    Make the code clear.

    As pointed out in Timmmm's answer, the Go compiler does generate identical code in both cases.

    0 讨论(0)
  • 2021-01-29 20:55

    Assuming that empty spaces and all leading and trailing white spaces should be removed:

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

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

    0 讨论(0)
提交回复
热议问题