Goのio.Readerから文字列へ


129

io.ReadCloserオブジェクトがあります(オブジェクトから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.

このコピーは保護メカニズムとして行われます。文字列は不変です。[]バイトを文字列に変換できれば、文字列の内容を変更できます。ただし、goでは、安全でないパッケージを使用してタイプセーフメカニズムを無効にすることができます。安全でないパッケージは自己責任で使用してください。うまくいけば、名前だけで十分な警告になります。これが私が安全でないことを使ってそれを行う方法です:

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

これで、バイト配列を文字列に効率的に変換できました。本当に、これはすべて、型システムをだまして文字列と呼ぶことです。この方法にはいくつかの注意点があります。

  1. これがすべてのgoコンパイラで機能するという保証はありません。これはplan-9 gcコンパイラで動作しますが、公式の仕様には記載されていない「実装の詳細」に依存しています。これがすべてのアーキテクチャで機能すること、またはgcで変更されないことさえ保証できません。つまり、これは悪い考えです。
  2. その文字列は変更可能です!そのバッファに対して呼び出しを行う、文字列変更されます。十分気をつける。

私のアドバイスは公式の方法に固執することです。コピーを行うことはないという高価な、それは危険なの悪価値がありません。文字列が大きすぎてコピーできない場合は、文字列にしないでください。


ありがとう、それは本当に詳細な答えです。「良い」方法は、@ Soniaの回答とほぼ同じように見えます(buf.Stringが内部的にキャストするだけなので)。
djd

1
そしてそれは私のバージョンでも動作しません、それは&but.Bytes()からポインターを取得することができないようです。Go1の使用。
sinni800

@ sinni800先端をありがとう。関数の戻り値がアドレス指定できなかったことを忘れました。現在は修正されています。
スティーブンウェインバーグ

3
まあコンピュータはバイトのブロックをコピーするのはかなり速いです。これがhttpリクエストであることを考えると、送信レイテンシがバイト配列のコピーにかかるわずかな時間よりも数十億倍も長くならないシナリオは想像できません。すべての関数型言語は、このタイプの不変のものをあちこちにコピーしますが、それでも十分高速に実行されます。
シャープに見

この回答は古くなっています。strings.Builder基になる[]byteリークが発生しないことを保証し、string今後サポートされる方法でコピーなしに変換することにより、これを効率的に実行します。これは2012年には存在しませんでした。以下の@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–したがって、2つのアプローチは実質的に同じです!
djd

1
これが、最も簡潔なソリューションです。
mk12 2013

1
私はこれをしました、そしてそれは働きます...初めてです。文字列を読み取った後、何らかの理由で後続の読み取りが空の文字列を返します。理由はまだわかりません。
Aldo 'xoen' Giambelluca '26

1
@ Aldo'xoen'Giambelluca ReadAllはリーダーを消費するため、次の呼び出しでは読み取るものはありません。
DanneJ 2016年


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)が呼び出されたときに何が起こっているのかを知っておくとよいでしょう。
ネイト

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.