io.Reader에서 Go의 문자열로


129

나는이 io.ReadCloser(AN에서 개체 http.Response개체).

전체 스트림을 string객체 로 변환하는 가장 효율적인 방법은 무엇입니까 ?

답변:


175

편집하다:

1.10부터 strings.Builder가 존재합니다. 예:

buf := new(strings.Builder)
n, err := io.Copy(buf, r)
// check errors
fmt.Println(buf.String())

아래의 오래된 정보

짧은 대답은 문자열로 변환하려면 바이트 배열의 전체 사본을 수행해야하기 때문에 비효율적이라는 것입니다. 원하는 것을 수행하는 올바른 (비효율적 인) 방법은 다음과 같습니다.

buf := new(bytes.Buffer)
buf.ReadFrom(yourReader)
s := buf.String() // Does a complete copy of the bytes in the buffer.

이 사본은 보호 메커니즘으로 수행됩니다. 문자열은 변경할 수 없습니다. [] 바이트를 문자열로 변환 할 수 있으면 문자열의 내용을 변경할 수 있습니다. 그러나, 안전하지 않은 패키지를 사용하여 유형 안전 메커니즘을 비활성화 할 수 있습니다. 안전하지 않은 패키지는 사용자 책임으로 사용하십시오. 바라건대 이름만으로도 충분히 경고 할 수 있습니다. 안전하지 않은 방법으로 수행하는 방법은 다음과 같습니다.

buf := new(bytes.Buffer)
buf.ReadFrom(yourReader)
b := buf.Bytes()
s := *(*string)(unsafe.Pointer(&b))

이제 바이트 배열을 문자열로 효율적으로 변환했습니다. 실제로이 모든 작업은 형식 시스템이 문자열을 호출하도록 속이는 것입니다. 이 방법에는 몇 가지주의 사항이 있습니다.

  1. 이것이 모든 컴파일러에서 작동한다는 보장은 없습니다. 이것은 plan-9 gc 컴파일러와 함께 작동하지만 공식 사양에 언급되지 않은 "구현 세부 사항"에 의존합니다. 모든 아키텍처에서 작동하거나 gc에서 변경되지 않을 수도 있습니다. 다시 말해, 이것은 나쁜 생각입니다.
  2. 그 문자열은 변경 가능합니다! 해당 버퍼를 호출 하면 문자열 변경 됩니다 . 정말 조심하세요.

내 조언은 공식적인 방법을 고수하는 것입니다. 사본을 만드는 것은 그렇게 비싸지 않으며 안전하지 않은 악의 가치가 없습니다. 문자열이 복사하기에 너무 큰 경우 문자열로 만들면 안됩니다.


고마워, 그것은 정말 자세한 답변입니다. "좋은"방법은 @Sonia의 답변과 대략 동등한 것으로 보입니다 (buf.String은 내부적으로 캐스트를 수행하기 때문에).
djd

1
그리고 그것은 내 버전에서도 작동하지 않으며 & but.Bytes ()에서 포인터를 얻을 수없는 것 같습니다. Go1 사용.
sinni800

@ sinni800 팁 주셔서 감사합니다. 함수 반환을 처리 할 수 ​​없다는 것을 잊었습니다. 이제 수정되었습니다.
Stephen Weinberg 1

3
글쎄, 컴퓨터는 바이트 블록을 복사하는 데 매우 빠릅니다. 그리고 이것이 http 요청이라면, 전송 대기 시간이 바이트 배열을 복사하는 데 걸리는 사소한 시간보다 큰 시간이 아닌 시나리오를 상상할 수 없습니다. 모든 기능적 언어는 이러한 유형의 불변의 내용을 모든 곳에서 복사하며 여전히 빠르게 실행됩니다.
더 선명

답변이 오래되었습니다. strings.Builder기본 []byte누수를 방지하고 string앞으로 지원되는 방식으로 사본없이 변환하여 효율적으로 수행합니다 . 아래의 @dimchansky 솔루션은 Go 1.10 이후 올바른 솔루션이었습니다. 수정을 고려하십시오!
Nuno Cruces

102

지금까지 답변은 질문의 "전체 스트림"부분을 다루지 않았습니다. 이 작업을 수행하는 좋은 방법은 ioutil.ReadAll입니다. 당신과 함께 io.ReaderCloser이름 rc, 나는 쓸 것이다,

if b, err := ioutil.ReadAll(rc); err == nil {
    return string(b)
} ...

2
고마워, 좋은 대답. buf.ReadFrom()전체 스트림을 EOF까지 읽는 것처럼 보입니다 .
djd

8
나는 단지의 구현을 읽는 방법 : 재미 ioutil.ReadAll()그것은 단순히 랩 bytes.Buffer'들 ReadFrom. 버퍼의 String()방법은 캐스팅에 대한 간단한 랩핑입니다. string따라서 두 가지 접근 방식은 거의 동일합니다!
djd

1
이것이 가장 간결한 솔루션입니다.
mk12

1
나는 이것을했고 그것이 처음으로 작동합니다. 문자열을 읽은 후 어떤 이유로 후속 읽기는 빈 문자열을 반환합니다. 왜 아직 확실하지 않습니다.
Aldo 'xoen'Giambelluca

1
@ Aldo'xoen'Giambelluca ReadAll은 독자를 소비하므로 다음 전화에는 읽을 내용이 없습니다.
DanneJ


5

가장 효율적인 방법은 항상 []byte대신에 사용하는 것입니다 string.

경우에는로부터 수신 된 데이터를 인쇄 할 필요 io.ReadCloserfmt패키지가 처리 할 수 []byte있지만, 때문에 효율적이지 않습니다 fmt구현이 내부적으로 변환됩니다 []bytestring. 이러한 변환을 피하기 위해 다음 fmt.Formatter과 같은 유형 의 인터페이스를 구현할 수 있습니다 type ByteSlice []byte.


[] 바이트에서 문자열로 변환하는 데 비용이 많이 듭니까? string ([] byte)가 실제로 [] byte를 복사하지는 않았지만 슬라이스 요소를 일련의 룬으로 해석했습니다. 그렇기 때문에 Buffer.String () weekly.golang.org/src/pkg/bytes/buffer.go?s=1787:1819#L37을 제안 했습니다 . string ([] byte)가 호출 될 때 무슨 일이 일어나고 있는지 아는 것이 좋을 것 같습니다.
Nate

4
에서 변환 []byte에는 string합리적으로 빠르지 만 문제는 "가장 효율적인 방법"에 대해 질문했다. 현재 Go 런타임은 string로 변환 []byte할 때 항상 새로운 것을 할당합니다 string. 그 이유는 컴파일러가 []byte변환 후 수정 여부를 결정하는 방법을 모르기 때문 입니다. 여기에는 컴파일러 최적화를위한 여지가 있습니다.

3
func copyToString(r io.Reader) (res string, err error) {
    var sb strings.Builder
    if _, err = io.Copy(&sb, r); err == nil {
        res = sb.String()
    }
    return
}


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.