这两种风格都在 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 == "" 编译为相同的高效代码。 ... 使代码清晰。
正如 Timmmm's answer 中所指出的,Go 编译器在这两种情况下都会生成相同的代码。
这似乎是过早的微优化。编译器可以自由地为这两种情况或至少为这两种情况生成相同的代码
if len(s) != 0 { ... }
和
if s != "" { ... }
因为语义显然是相等的。
假设应该删除空格和所有前导和尾随空格:
import "strings"
if len(strings.TrimSpace(s)) == 0 { ... }
因为:
len("") // is 0
len(" ") // one empty space is 1
len(" ") // two empty spaces is 2
< 1
+1
检查长度是一个很好的答案,但您也可以考虑一个“空”字符串,它也只是空格。不是“技术上”空的,但如果您愿意检查:
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!")
}
}
TrimSpace
将从原始字符串分配和复制一个新字符串,因此这种方法会大规模引入低效率。
s
是字符串类型,s[0:i]
返回一个新副本的情况下才是正确的。字符串在 Go 中是不可变的,所以它需要在这里创建一个副本吗?
strings.TrimSpace( s )
将不会导致新的字符串分配和字符复制,但如果字符串确实需要修剪,则将调用额外的副本(没有空白字符)。
gocritic
linter 建议使用 strings.TrimSpace(str) == ""
而不是长度检查。
到目前为止,Go 编译器在这两种情况下都会生成相同的代码,所以这是个人喜好问题。 GCCGo 确实会生成不同的代码,但几乎没有人使用它,所以我不会担心。
根据官方指南,从性能的角度来看,它们看起来是等效的 (ANisus answer),由于语法优势,s != "" 会更好。如果变量不是字符串,s != "" 将在编译时失败,而 len(s) == 0 将通过其他几种数据类型。
len()
,也只需要一点额外的工作.然而,我们曾经在 C 中做的一件事是将左侧强制转换为 const
或将静态字符串放在运算符的左侧以防止 s=="" 变成 s="" ,这在 C 语法中是可以接受的......而且可能也是golang。 (见扩展如果)
我认为 == ""
更快且更具可读性。
package main
import(
"fmt"
)
func main() {
n := 1
s:=""
if len(s)==0{
n=2
}
fmt.Println("%d", n)
}
当 dlv debug playground.go
cmp 与 len(s) 和 =="" 我得到这个 s == "" 情况
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)
块引用
使用如下函数会更简洁,更不容易出错:
func empty(s string) bool {
return len(strings.TrimSpace(s)) == 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 个字符字符串(前缀变体)更快。
这比修剪整个字符串更高效,因为您只需要检查至少一个非空格字符是否存在
// 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
}
我认为最好的方法是与空白字符串进行比较
BenchmarkStringCheck1 正在检查空白字符串
BenchmarkStringCheck2 正在检查 len 零
我检查空和非空字符串检查。您可以看到使用空白字符串进行检查更快。
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 {
}
}
}
if mystring != "" { }
是当今最好、首选和惯用的方式。标准库包含其他内容的原因是因为它是在 2010 年之前编写的,当时len(mystring) == 0
优化有意义。len
不到 1 年的提交来检查空/非空字符串。就像 Brad Fitzpatrick 的 commit。恐怕这仍然是品味和清晰度的问题;)len(v) > 0
(第 2702 行)。我相信它不会自动显示,因为它是从 golang.org/x/net/http2 生成的。