วิธีใดที่ดีที่สุด (มีความหมายมากกว่า) สำหรับการทดสอบสตริงที่ไม่ว่าง (ใน Go)
if len(mystring) > 0 { }
หรือ:
if mystring != "" { }
หรืออย่างอื่น?
วิธีใดที่ดีที่สุด (มีความหมายมากกว่า) สำหรับการทดสอบสตริงที่ไม่ว่าง (ใน Go)
if len(mystring) > 0 { }
หรือ:
if mystring != "" { }
หรืออย่างอื่น?
คำตอบ:
สไตล์ทั้งสองจะใช้ในไลบรารีมาตรฐานของ 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 :
อันที่ทำให้รหัสนั้นชัดเจน
หากฉันกำลังจะดูองค์ประกอบ x ฉันมักจะเขียน
len (s)> x, แม้สำหรับ x == 0 แต่ถ้าฉันสนใจ
"มันเป็นสตริงที่เฉพาะเจาะจงนี้" ฉันมักจะเขียน s == ""มีเหตุผลที่จะสมมติว่าคอมไพเลอร์ที่เป็นผู้ใหญ่จะรวบรวม
len (s) == 0 และ s == "" ให้เป็นรหัสเดียวกันและมีประสิทธิภาพ
...ทำให้รหัสชัดเจน
ดังที่ระบุไว้ในคำตอบของ Timmmmคอมไพเลอร์ Go สร้างโค้ดเหมือนกันในทั้งสองกรณี
len(v) > 0
ในh2_bundle.go (บรรทัด 2702) ไม่แสดงโดยอัตโนมัติเนื่องจากฉันสร้างขึ้นจาก golang.org/x/net/http2 ฉันเชื่อ
ดูเหมือนว่าจะเป็นการทำให้หมดเวลาก่อนกำหนดขนาดเล็ก คอมไพเลอร์มีอิสระในการผลิตรหัสเดียวกันสำหรับทั้งสองกรณีหรืออย่างน้อยสำหรับทั้งสองนี้
if len(s) != 0 { ... }
และ
if s != "" { ... }
เพราะความหมายมีความชัดเจนเท่าเทียมกัน
การตรวจสอบความยาวเป็นคำตอบที่ดี แต่คุณสามารถอธิบายถึงสตริง "ว่าง" ที่เป็นเพียงช่องว่างเท่านั้น ไม่ใช่ "ทางเทคนิค" ว่างเปล่า แต่ถ้าคุณสนใจที่จะตรวจสอบ:
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) == ""
แทนการตรวจสอบความยาว
สมมติว่าควรลบช่องว่างและช่องว่างนำหน้าและช่องว่างสีขาวทั้งหมด:
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
ณ ตอนนี้คอมไพเลอร์ Go สร้างโค้ดที่เหมือนกันในทั้งสองกรณีดังนั้นจึงเป็นเรื่องของรสนิยม GCCGo สร้างรหัสที่แตกต่างกัน แต่แทบจะไม่มีใครใช้มันดังนั้นฉันจะไม่กังวลเกี่ยวกับสิ่งนั้น
มันจะสะอาดกว่าและมีข้อผิดพลาดน้อยกว่าเมื่อใช้ฟังก์ชั่นแบบเดียวกับด้านล่าง:
func empty(s string) bool {
return len(strings.TrimSpace(s)) == 0
}
เพียงเพิ่มมากขึ้นเพื่อแสดงความคิดเห็น
ส่วนใหญ่เกี่ยวกับวิธีการทดสอบประสิทธิภาพ
ฉันทดสอบด้วยรหัสต่อไปนี้:
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) โดยใช้ตัวสร้างสตริงที่มีการแจกแจงที่เกี่ยวข้อง และมีความยาวแปรผันด้วย
ดังนั้นฉันจึงไม่มีความมั่นใจในประสิทธิภาพที่แตกต่างระหว่างวิธีการหลักในการทดสอบสตริงว่างขณะเดินทาง
และฉันสามารถระบุด้วยความมั่นใจได้เร็วกว่าที่จะไม่ทดสอบสตริงว่างเลยกว่าทดสอบสตริงว่าง และมันก็เร็วกว่าที่จะทดสอบสตริงว่างเปล่ามากกว่าที่จะทดสอบ 1 อักขระสตริง (ตัวแปรคำนำหน้า)
ตามแนวทางอย่างเป็นทางการและจากมุมมองการปฏิบัติงานพวกเขาปรากฏว่าเทียบเท่า ( ANisus คำตอบ ) s! = "" จะดีกว่าเนื่องจากข้อได้เปรียบด้านการสร้างประโยค s! = "" จะล้มเหลว ณ เวลารวบรวมหากตัวแปรไม่ใช่สตริงในขณะที่ len (s) == 0 จะส่งผ่านสำหรับชนิดข้อมูลอื่น ๆ
len()
ก็ต้องใช้งานเพิ่มอีกเล็กน้อย อย่างไรก็ตามสิ่งหนึ่งที่เราเคยทำใน C ถูกโยนด้านซ้ายไปที่ a const
หรือใส่สตริงแบบคงที่ที่ด้านซ้ายของผู้ประกอบการเพื่อป้องกัน s == "" จากการกลายเป็น s = "" ซึ่งในไวยากรณ์ C เป็นที่ยอมรับ .. และอาจ golang ด้วย (ดูส่วนขยายหาก)
สิ่งนี้จะมีประสิทธิภาพมากกว่าการตัดทั้งสตริงเนื่องจากคุณจะต้องตรวจสอบอย่างน้อยอักขระที่ไม่ใช่ช่องว่างที่มีอยู่
// 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 zero
ฉันตรวจสอบด้วยการตรวจสอบสตริงว่างและไม่ว่าง คุณจะเห็นว่าการตรวจสอบด้วยสตริงว่างเร็วขึ้น
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
เพิ่มประสิทธิภาพเหมาะสม