Go에서 빈 문자열을 테스트하는 가장 좋은 방법은 무엇입니까?


261

비어 있지 않은 문자열을 테스트하는 데 가장 적합한 방법은 무엇입니까?

if len(mystring) > 0 { }

또는:

if mystring != "" { }

또는 다른 것?

답변:


389

두 가지 스타일 모두 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를 보려고하면 일반적으로
x == 0 인 경우에도 len (s)> x를 쓰지만
"이 특정 문자열"에 관심 이 있다면 s == ""를 쓰는 경향이 있습니다.

성숙한 컴파일러가
len (s) == 0 및 s == ""를 동일한 효율적인 코드로 컴파일한다고 가정하는 것이 합리적 입니다.
...

코드를 명확하게하십시오.

Timmmm의 답변 에서 지적했듯이 Go 컴파일러는 두 경우 모두 동일한 코드를 생성합니다.


1
이 답변에 동의하지 않습니다. 간단하게 if mystring != "" { }최고에, 선호와 관용적 방법 오늘이다. 표준 라이브러리에 다른 이유가 포함되어있는 이유는 2010 년 이전에 len(mystring) == 0최적화가 적합하게 작성 되었기 때문 입니다.
honzajde

12
@honzajde 문장의 유효성을 검사하려고 시도했지만 len비어 있거나 비어 있지 않은 문자열을 확인하기 위해 1 년 미만의 표준 라이브러리에서 커밋이 발견되었습니다 . Brad Fitzpatrick의 커밋 과 같습니다 . 나는 그것이 여전히 맛과 선명도의 문제인 것을 두려워한다.;)
ANisus

6
@honzajde 트롤링하지 않습니다. 커밋에 3 개의 len 키워드가 있습니다. h2_bundle.go (라인 2702) len(v) > 0에서 언급했습니다 . golang.org/x/net/http2에서 생성되므로 자동으로 표시되지 않습니다.
ANisus

2
그것이 diff에 noi라면 새로운 것이 아닙니다. 직접 링크를 게시하지 않으시겠습니까? 어쨌든 나를위한 충분한 탐정 작업 ... 나는 그것을 보지 못한다.
honzajde

6
@honzajde 걱정 마세요. h2_bundle.go 파일에 대해 "Load diff"를 클릭하는 방법을 다른 사람들이 알고 있다고 가정합니다.
ANisus

30

이것은 조기 미세 최적화로 보입니다. 컴파일러는 두 경우 모두 또는 적어도 두 가지 모두에 대해 동일한 코드를 자유롭게 생성 할 수 있습니다

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

if s != "" { ... }

의미론이 분명히 동일하기 때문입니다.


1
동의하지만, 그것은 실제로 문자열의 구현에 달려 있습니다 ... 문자열이 파스칼처럼 구현되면 len (s)은 o (1)에서 실행되고 C와 같은 경우 o (n)입니다. 또는 len ()이 완료되기까지 실행해야하기 때문에.
Richard

컴파일러가 이것을 예상하는지 또는 컴파일러가 이것을 구현할 수 있다고 제안하고 있는지 확인하기 위해 코드 생성을 살펴 보셨습니까?
Michael Labbé

19

길이를 확인하는 것이 좋은 대답이지만 공백 만있는 "빈"문자열을 설명 할 수도 있습니다. "기술적으로"비어 있지는 않지만 확인해야 할 경우 :

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원래 문자열에서 새 문자열을 할당하고 복사하므로이 방법으로 인해 비 효율성이 발생합니다.
Dai

@Dai 소스 코드를 보면, 주어진 s문자열 유형 인 경우에만 s[0:i]새 사본을 리턴하는 경우에만 적용됩니다 . Go에서는 문자열을 변경할 수 없으므로 여기에서 복사본을 만들어야합니까?
Michael Paesold

@MichaelPaesold Right- strings.TrimSpace( s )문자열을 다듬을 필요가없는 경우 새 문자열 할당 및 문자 복사를 유발하지 않지만 문자열을 다듬어야하는 경우 추가 공백 (공백 문자없이)이 호출됩니다.
다이

1
"기술적으로 비어있다"는 질문입니다.
Richard

gocritic린터 사용하여 제안 strings.TrimSpace(str) == ""대신 길이 검사의.
y3sh

12

빈 공간과 모든 선행 및 후행 공백을 제거해야한다고 가정합니다.

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

때문에 :
len("") // is 0
len(" ") // one empty space is 1
len(" ") // two empty spaces is 2


2
왜이 가정이 있습니까? 남자는 빈 줄에 대해 분명히 이야기합니다. 문자열에서 ASCII 문자 만 원한다고 가정하고 ASCII가 아닌 모든 문자를 제거하는 함수를 추가한다고 가정 할 때와 같은 방법입니다.
살바도르 달리

1
len ( ""), len ( "") 및 len ( "")은 같은 것이 아닙니다. 나는 그가 이전에 그중 하나에 초기화 한 변수가 실제로 "기술적으로"비어 있는지 확인하기를 원한다고 가정했습니다.
Edwinner

이것은 실제로이 게시물에서 필요한 것입니다. 공백이 아닌 문자를 하나 이상 보유하려면 사용자 입력이 필요 하며이 한 줄짜리가 명확하고 간결합니다. 내가해야 할 일은 if 조건을 < 1+1로 만드는 것입니다
Shadoninja

7

현재 Go 컴파일러는 두 경우 모두 동일한 코드를 생성하므로 맛의 문제입니다. GCCGo는 다른 코드를 생성하지만 간신히 누군가가 사용하므로 걱정하지 않습니다.

https://godbolt.org/z/fib1x1



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) 만 있습니다.

그리고 전체 로그를 보면 시도 간의 차이가 벤치 마크 함수의 차이보다 큽니다.

또한 후자의 변형이 2 번이 아닌 6 번 증가해야하더라도 BenchmarkStringCheckEq와 BenchmarkStringCheckNe 또는 BenchmarkStringCheckLen 및 BenchmarkStringCheckLenGt간에 측정 가능한 차이가없는 것으로 보입니다.

수정 된 테스트 또는 내부 루프로 테스트를 추가하여 동일한 성능에 대한 확신을 얻을 수 있습니다. 이것은 더 빠릅니다.

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 개의 문자열 (접두사 변형)을 테스트하는 것보다 빈 문자열을 테스트하는 것이 더 빠릅니다.


0

공식적인 지침과 성능 관점에서 볼 때 그것들은 동등한 것으로 보이며 ( ANisus answer ), 구문 상 이점 때문에 s! = ""가 더 좋습니다. s! = ""는 변수가 문자열이 아닌 경우 컴파일 타임에 실패하지만 len (s) == 0은 다른 여러 데이터 유형에 전달됩니다.


CPU 사이클을 세고 C 컴파일러가 C 및 Pascal 문자열의 구조를 생성하고 깊이 이해 한 어셈블러를 검토 한 시간이있었습니다. 세계의 모든 최적화에도 len()약간의 추가 작업이 필요합니다. 그러나 C에서 사용했던 한 가지 일은 왼쪽을 a로 캐스트 const하거나 연산자의 왼쪽에 정적 문자열을 넣어 s == ""가 s = ""가되는 것을 방지하기 위해 C 구문에서 허용됩니다. .. 그리고 아마도 golang도. (확장 된 경우 참조)
Richard

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

3
@Richard 일 수도 있지만 "문자열이 비어있는 경우 golang check"또는 이와 유사한 일에 대해 인터넷 검색을 할 때 이것이 유일하게 제기되는 질문이므로 사람들에게는 이것이 전례없는 일이 아닙니다. 스택 교환
Brian Leishman

-1

가장 좋은 방법은 빈 문자열과 비교하는 것입니다.

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 {

        }
    }
}

5
나는이 증거가 아무것도 없다고 생각한다. 테스트 할 때 컴퓨터가 다른 작업을 수행하기 때문에 차이가 작기 때문에 하나가 더 빠릅니다. 이것은 두 함수가 동일한 호출로 컴파일되었음을 암시 할 수 있습니다.
SR
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.