Odpowiedzi:
Oba style są używane w standardowych bibliotekach Go.
if len(s) > 0 { ... }
można znaleźć w strconv
pakiecie: http://golang.org/src/pkg/strconv/atoi.go
if s != "" { ... }
można znaleźć w encoding/json
pakiecie: http://golang.org/src/pkg/encoding/json/encode.go
Oba są idiomatyczne i wystarczająco jasne. Jest to bardziej kwestia osobistego gustu i przejrzystości.
Russ Cox pisze w wątku golang-nuts :
Ten, który wyjaśnia kod.
Jeśli mam spojrzeć na element x, zwykle piszę
len (s)> x, nawet dla x == 0, ale jeśli mnie to obchodzi
„czy to ten konkretny ciąg„ mam tendencję do pisania s == ””.Można założyć, że dojrzały kompilator skompiluje
len (s) == 0 is s == "" do tego samego, wydajnego kodu.
...Wyczyść kod.
Jak wskazano w odpowiedzi Timmmm , kompilator Go generuje identyczny kod w obu przypadkach.
len
do sprawdzania pustych / niepustych ciągów. Tak jak to zrobił Brad Fitzpatrick. Obawiam się, że wciąż jest to kwestia gustu i jasności;)
len(v) > 0
w h2_bundle.go (linia 2702). Uważam, że nie jest automatycznie wyświetlany, ponieważ jest generowany z golang.org/x/net/http2.
To wydaje się być przedwczesną mikrooptymalizacją. Kompilator może generować ten sam kod dla obu przypadków lub przynajmniej dla tych dwóch
if len(s) != 0 { ... }
i
if s != "" { ... }
ponieważ semantyka jest wyraźnie równa.
Sprawdzanie długości jest dobrą odpowiedzią, ale można również uwzględnić „pusty” ciąg znaków, który jest również tylko białymi spacjami. Nie „technicznie” pusty, ale jeśli chcesz sprawdzić:
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
przydzieli i skopiuje nowy ciąg z oryginalnego ciągu, więc to podejście wprowadzi nieefektywności na dużą skalę.
s
ciąg typu s[0:i]
zwraca nową kopię. Ciągi są niezmienne w Go, więc czy trzeba tutaj utworzyć kopię?
strings.TrimSpace( s )
nie spowoduje przydzielenia nowego ciągu i kopiowania znaków, jeśli ciąg nie wymaga przycinania, ale jeśli ciąg wymaga przycinania, zostanie wywołana dodatkowa kopia (bez znaków spacji).
gocritic
Linter sugeruje użycie strings.TrimSpace(str) == ""
zamiast czeku długości.
Zakładając, że należy usunąć puste spacje oraz wszystkie wiodące i końcowe białe spacje:
import "strings"
if len(strings.TrimSpace(s)) == 0 { ... }
Ponieważ :
len("") // is 0
len(" ") // one empty space is 1
len(" ") // two empty spaces is 2
< 1
+1
Na razie kompilator Go generuje identyczny kod w obu przypadkach, więc jest to kwestia gustu. GCCGo generuje inny kod, ale prawie nikt go nie używa, więc nie martwię się o to.
Użycie funkcji takiej jak ta poniżej byłoby czystsze i mniej podatne na błędy:
func empty(s string) bool {
return len(strings.TrimSpace(s)) == 0
}
Aby dodać więcej do komentarza
Głównie o tym, jak przeprowadzić testy wydajności.
Testowałem przy użyciu następującego kodu:
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)
}
}
I wyniki były:
% 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
Skutecznie warianty zwykle nie osiągają najszybszego czasu i istnieje tylko minimalna różnica (około 0,01ns / operacja) między wariantem prędkości maksymalnej.
A jeśli zajrzę do pełnego dziennika, różnica między próbami jest większa niż różnica między funkcjami testu porównawczego.
Nie wydaje się również, aby istniała jakakolwiek mierzalna różnica między BenchmarkStringCheckEq i BenchmarkStringCheckNe lub BenchmarkStringCheckLen i BenchmarkStringCheckLenGt, nawet jeśli te ostatnie warianty powinny zawierać c 6 razy zamiast 2 razy.
Możesz spróbować uzyskać pewność co do równej wydajności, dodając testy ze zmodyfikowanym testem lub pętlą wewnętrzną. Jest to szybsze:
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)
}
}
To nie jest szybsze:
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)
}
}
Oba warianty są zwykle szybsze lub wolniejsze niż różnica między głównymi testami.
Dobrze byłoby również wygenerować łańcuchy testowe (ss) przy użyciu generatora łańcuchów o odpowiednim rozkładzie. I mają też różne długości.
Więc nie mam żadnej pewności co do różnicy wydajności między głównymi metodami testowania pustego łańcucha w ruchu.
Mogę stwierdzić z pewną pewnością, że szybciej nie testować pustego łańcucha niż testować pusty łańcuch. A także szybsze jest testowanie pustego łańcucha niż testowanie 1 łańcucha znaków (wariant prefiksu).
Zgodnie z oficjalnymi wytycznymi iz punktu widzenia wydajności wydają się one równoważne ( odpowiedź ANisus ), s! = "" Byłoby lepsze ze względu na przewagę syntaktyczną. s! = "" zakończy się niepowodzeniem w czasie kompilacji, jeśli zmienna nie jest łańcuchem, a len (s) == 0 przejdzie dla kilku innych typów danych.
len()
wymaga tylko trochę więcej pracy. JEDNAK, jedną rzeczą, którą robiliśmy w C, było przeniesienie lewej strony na a const
lub umieszczenie ciągu statycznego po lewej stronie operatora, aby zapobiec s == "" zamienianiu się w s = "", co w składni C jest dopuszczalne. .. i prawdopodobnie również Golanga. (patrz przedłużony, jeśli)
Byłoby to bardziej wydajne niż przycinanie całego łańcucha, ponieważ wystarczy sprawdzić przynajmniej jeden istniejący znak spacji
// 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
}
Myślę, że najlepszym sposobem jest porównanie z pustym ciągiem
BenchmarkStringCheck1 sprawdza pusty ciąg znaków
BenchmarkStringCheck2 sprawdza z długością zerową
Sprawdzam za pomocą sprawdzania pustych i niepustych ciągów. Widać, że sprawdzanie pustym ciągiem jest szybsze.
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
Kod
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 != "" { }
jest to najlepszy, preferowany i idiomatyczny sposób DZISIAJ. Powodem, dla którego standardowa biblioteka zawiera inaczej, jest to, że została napisana przed 2010 r., Kiedylen(mystring) == 0
optymalizacja miała sens.