Cách tốt nhất để kiểm tra một chuỗi trống trong Go là gì?


261

Phương pháp nào là tốt nhất (đơn giản hơn) để kiểm tra các chuỗi không trống (trong Go)?

if len(mystring) > 0 { }

Hoặc là:

if mystring != "" { }

Hay cái gì khác?

Câu trả lời:


389

Cả hai phong cách đều được sử dụng trong các thư viện tiêu chuẩn của Go.

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

có thể được tìm thấy trong strconvgói: http://golang.org/src/pkg/strconv/atoi.go

if s != "" { ... }

có thể được tìm thấy trong encoding/jsongói: http://golang.org/src/pkg/encoding/json/encode.go

Cả hai đều là thành ngữ và đủ rõ ràng. Đó là nhiều hơn một vấn đề của sở thích cá nhân và về sự rõ ràng.

Russ Cox viết trong một chủ đề golang-nut :

Một trong đó làm cho mã rõ ràng.
Nếu tôi sắp xem xét phần tử x, tôi thường viết
len (s)> x, ngay cả đối với x == 0, nhưng nếu tôi quan tâm đến
"thì đây có phải là chuỗi cụ thể" Tôi có xu hướng viết s == "".

Thật hợp lý khi giả định rằng một trình biên dịch trưởng thành sẽ biên dịch
len (s) == 0 và s == "" thành cùng một mã hiệu quả.
...

Làm cho mã rõ ràng.

Như đã chỉ ra trong câu trả lời của Timmmm , trình biên dịch Go sẽ tạo mã giống hệt nhau trong cả hai trường hợp.


1
Tôi không đồng ý với câu trả lời này. Đơn giản if mystring != "" { }là cách tốt nhất, ưa thích và thành ngữ HÔM NAY. Lý do thư viện chuẩn chứa khác là vì nó được viết trước năm 2010 khi len(mystring) == 0tối ưu hóa có ý nghĩa.
honzajde

12
@honzajde Chỉ cần cố gắng xác thực tuyên bố của bạn, nhưng tìm thấy các cam kết trong thư viện chuẩn dưới 1 năm bằng cách sử dụng lenđể kiểm tra các chuỗi trống / không trống. Giống như cam kết này của Brad Fitzpatrick. Tôi sợ đó vẫn là vấn đề của hương vị và sự rõ ràng;)
ANisus

6
@honzajde Không troll. Có 3 từ khóa len trong cam kết. Tôi đã đề cập đến len(v) > 0trong h2_bundle.go (dòng 2702). Nó không tự động được hiển thị vì nó được tạo ra từ golang.org/x/net/http2, tôi tin.
ANisus

2
Nếu nó là noi trong diff thì nó không mới. Tại sao bạn không đăng liên kết trực tiếp? Dù sao đi nữa. đủ công việc thám tử cho tôi ... tôi không thấy nó.
honzajde

6
@honzajde Đừng lo lắng. Tôi giả sử những người khác sẽ biết cách nhấp vào "Tải diff" cho tệp h2_bundle.go.
ANisus

30

Điều này dường như là vi mô sớm. Trình biên dịch có thể tự do tạo ra cùng một mã cho cả hai trường hợp hoặc ít nhất là cho hai trường hợp này

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

if s != "" { ... }

bởi vì ngữ nghĩa rõ ràng là bằng nhau


1
đồng ý, tuy nhiên, nó thực sự phụ thuộc vào việc triển khai chuỗi ... Nếu các chuỗi được triển khai như pascal thì len (s) được thực thi trong o (1) và nếu như C thì đó là o (n). hoặc bất cứ điều gì, vì len () phải thực thi để hoàn thành.
Richard

Bạn đã xem xét việc tạo mã để xem trình biên dịch có lường trước được điều này không hay bạn chỉ gợi ý rằng trình biên dịch có thể thực hiện việc này không?
Michael Labbé

19

Kiểm tra độ dài là một câu trả lời tốt, nhưng bạn cũng có thể tính đến một chuỗi "trống" cũng chỉ là khoảng trắng. Không "kỹ thuật" trống, nhưng nếu bạn quan tâm để kiểm tra:

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

TrimSpacesẽ phân bổ và sao chép một chuỗi mới từ chuỗi ban đầu, vì vậy phương pháp này sẽ đưa ra sự không hiệu quả ở quy mô.
Đại

@Dai nhìn vào mã nguồn, điều đó chỉ đúng nếu, được đưa ra slà kiểu chuỗi, s[0:i]trả về một bản sao mới. Chuỗi là bất biến trong Go, vì vậy nó có cần tạo một bản sao ở đây không?
Michael Paesold

@MichaelPaesold Right - strings.TrimSpace( s )sẽ không gây ra sự phân bổ chuỗi và sao chép ký tự mới nếu chuỗi không cần cắt xén, nhưng nếu chuỗi cần cắt xén thì bản sao bổ sung (không có ký tự khoảng trắng) sẽ được gọi.
Đại

1
"Kỹ thuật trống rỗng" là câu hỏi.
Richard

Kẻ nói gocriticdối đề nghị sử dụng strings.TrimSpace(str) == ""thay vì kiểm tra độ dài.
y3sh

12

Giả sử rằng các khoảng trắng trống và tất cả các khoảng trắng ở đầu và cuối phải được loại bỏ:

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

Bởi vì :
len("") // is 0
len(" ") // one empty space is 1
len(" ") // two empty spaces is 2


2
Tại sao bạn có giả định này? Chàng trai kể rõ ràng về chuỗi trống. Giống như cách bạn có thể nói, giả sử rằng bạn chỉ muốn các ký tự ascii trong một chuỗi và sau đó thêm một hàm loại bỏ tất cả các ký tự không phải mã ascii.
Salvador Dali

1
Bởi vì len (""), len ("") và len ("") không giống nhau. Tôi đã giả định rằng anh ta muốn chắc chắn rằng một biến mà anh ta đã khởi tạo cho một trong những biến trước đó thực sự vẫn còn "về mặt kỹ thuật".
Edwinner

Đây thực sự là chính xác những gì tôi cần từ bài viết này. Tôi cần đầu vào của người dùng để có ít nhất 1 ký tự không phải khoảng trắng và lớp lót này rõ ràng và súc tích. Tất cả những gì tôi cần làm là thực hiện điều kiện if < 1+1
Shadoninja

7

Cho đến nay, trình biên dịch Go tạo mã giống hệt nhau trong cả hai trường hợp, vì vậy đây là vấn đề của hương vị. GCCGo không tạo mã khác nhau, nhưng hầu như không ai sử dụng nó nên tôi sẽ không lo lắng về điều đó.

https://godbolt.org/z/fib1x1


1

Nó sẽ sạch hơn và ít bị lỗi hơn khi sử dụng một chức năng như dưới đây:

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

0

Chỉ cần thêm nhiều hơn để bình luận

Chủ yếu là về cách làm kiểm tra hiệu suất.

Tôi đã thử nghiệm với mã sau đây:

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

Và kết quả là:

% 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

Các biến thể hiệu quả thường không đạt được thời gian nhanh nhất và chỉ có sự khác biệt tối thiểu (khoảng 0,01ns / op) giữa tốc độ tối đa của biến thể.

Và nếu tôi xem nhật ký đầy đủ, sự khác biệt giữa các lần thử lớn hơn sự khác biệt giữa các hàm điểm chuẩn.

Ngoài ra, dường như không có bất kỳ sự khác biệt có thể đo lường nào giữa BenchmarkStringCheckEq và BenchmarkStringCheckNe hoặc BenchmarkStringCheckLen và BenchmarkStringCheckLenGt ngay cả khi các biến thể sau nên thay đổi 6 lần thay vì 2 lần.

Bạn có thể cố gắng để có được sự tự tin về hiệu suất tương đương bằng cách thêm các bài kiểm tra với bài kiểm tra sửa đổi hoặc vòng lặp bên trong. Cái này nhanh hơn:

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

Đây không phải là nhanh hơn:

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

Cả hai biến thể thường nhanh hơn hoặc chậm hơn so với sự khác biệt giữa các thử nghiệm chính.

Cũng có thể tạo chuỗi thử nghiệm (ss) bằng cách sử dụng trình tạo chuỗi với phân phối có liên quan. Và có độ dài thay đổi quá.

Vì vậy, tôi không có bất kỳ sự tự tin nào về sự khác biệt hiệu năng giữa các phương thức chính để kiểm tra chuỗi rỗng.

Và tôi có thể tự tin khẳng định, sẽ nhanh hơn khi không kiểm tra chuỗi trống nào hơn là kiểm tra chuỗi trống. Và cũng nhanh hơn để kiểm tra chuỗi rỗng so với kiểm tra 1 chuỗi char (biến thể tiền tố).


0

Theo hướng dẫn chính thức và theo quan điểm hiệu suất, chúng có vẻ tương đương ( câu trả lời của ANisus ), s! = "" Sẽ tốt hơn do lợi thế cú pháp. s! = "" sẽ thất bại tại thời gian biên dịch nếu biến không phải là một chuỗi, trong khi len (s) == 0 sẽ truyền cho một số loại dữ liệu khác.


Đã có lúc tôi đếm các chu kỳ CPU và xem xét trình biên dịch mà trình biên dịch C tạo ra và hiểu sâu sắc cấu trúc của chuỗi C và Pascal ... ngay cả với tất cả các tối ưu hóa trên thế giới len()chỉ cần thêm một chút công việc. TUY NHIÊN, một điều chúng ta thường làm trong C đã chuyển bên trái sang a consthoặc đặt chuỗi tĩnh ở bên trái của toán tử để ngăn s == "" trở thành s = "" trong cú pháp C có thể chấp nhận được. .. và có lẽ golang cũng vậy. (xem phần mở rộng nếu)
Richard

-1

Điều này sẽ hiệu quả hơn so với cắt xén toàn bộ chuỗi, vì bạn chỉ cần kiểm tra ít nhất một ký tự không phải không gian duy nhất hiện có

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

3
@Richard có thể như vậy, nhưng khi Google cho "golang kiểm tra xem chuỗi có trống không" hay những thứ tương tự, đây là câu hỏi duy nhất xuất hiện, vì vậy đối với những người này, đây không phải là điều chưa từng có Trao đổi ngăn xếp
Brian Leishman

-1

Tôi nghĩ cách tốt nhất là so sánh với chuỗi trống

BenchmarkStringCheck1 đang kiểm tra với chuỗi trống

BenchmarkStringCheck2 đang kiểm tra với len zero

Tôi kiểm tra với kiểm tra chuỗi trống và không trống. Bạn có thể thấy rằng kiểm tra với một chuỗi trống nhanh hơn.

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 {

        }
    }
}

5
Tôi nghĩ bằng chứng này không có gì. Vì máy tính của bạn làm những việc khác khi kiểm tra và sự khác biệt là nhỏ để nói cái này nhanh hơn cái khác. Điều này có thể gợi ý rằng cả hai chức năng đã được biên dịch cho cùng một cuộc gọi.
SR
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.