Von io.Reader zu String in Go


128

Ich habe ein io.ReadCloserObjekt (von einem http.ResponseObjekt).

Was ist der effizienteste Weg, um den gesamten Stream in ein stringObjekt zu konvertieren ?

Antworten:


174

BEARBEITEN:

Seit 1.10 existiert strings.Builder. Beispiel:

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

Veraltete Informationen unten

Die kurze Antwort lautet, dass dies nicht effizient ist, da für die Konvertierung in eine Zeichenfolge eine vollständige Kopie des Byte-Arrays erforderlich ist. Hier ist der richtige (nicht effiziente) Weg, um das zu tun, was Sie wollen:

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

Diese Kopie dient als Schutzmechanismus. Saiten sind unveränderlich. Wenn Sie ein [] Byte in eine Zeichenfolge konvertieren könnten, könnten Sie den Inhalt der Zeichenfolge ändern. Mit go können Sie jedoch die Typensicherheitsmechanismen mithilfe des unsicheren Pakets deaktivieren. Verwenden Sie das unsichere Paket auf eigenes Risiko. Hoffentlich ist der Name allein eine gute Warnung. So würde ich es mit unsicher machen:

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

Jetzt haben Sie Ihr Byte-Array effizient in einen String konvertiert. Wirklich, alles, was dies tut, ist das Typsystem dazu zu bringen, es eine Zeichenfolge zu nennen. Diese Methode weist einige Einschränkungen auf:

  1. Es gibt keine Garantie dafür, dass dies in allen Go-Compilern funktioniert. Dies funktioniert zwar mit dem Plan-9-GC-Compiler, stützt sich jedoch auf "Implementierungsdetails", die in der offiziellen Spezifikation nicht erwähnt sind. Sie können nicht einmal garantieren, dass dies auf allen Architekturen funktioniert oder nicht in gc geändert wird. Mit anderen Worten, das ist eine schlechte Idee.
  2. Diese Zeichenfolge ist veränderlich! Wenn Sie diesen Puffer aufrufen, wird die Zeichenfolge geändert. Sei sehr vorsichtig.

Mein Rat ist, sich an die offizielle Methode zu halten. Eine Kopie zu machen ist nicht so teuer und das Übel der Unsicherheit nicht wert. Wenn die Zeichenfolge zu groß ist, um eine Kopie zu erstellen, sollten Sie sie nicht zu einer Zeichenfolge machen.


Danke, das ist eine sehr detaillierte Antwort. Der "gute" Weg scheint auch ungefähr der Antwort von @ Sonia zu entsprechen (da buf.String die Besetzung nur intern übernimmt).
DJD

1
Und es funktioniert nicht einmal mit meiner Version, es scheint nicht in der Lage zu sein, einen Zeiger von & but.Bytes () zu bekommen. Verwenden von Go1.
sinni800

@ sinni800 Danke für den Tipp. Ich habe vergessen, dass Funktionsrückgaben nicht adressierbar waren. Es ist jetzt behoben.
Stephen Weinberg

3
Nun, Computer sind verdammt schnell darin, Byteblöcke zu kopieren. Und da dies eine http-Anfrage ist, kann ich mir kein Szenario vorstellen, in dem die Übertragungslatenz nicht eine Billion Mal größer ist als die triviale Zeit, die zum Kopieren des Byte-Arrays benötigt wird. Jede funktionale Sprache kopiert diese Art von unveränderlichem Material überall und läuft immer noch schnell.
Siehe schärfer

Diese Antwort ist veraltet. strings.BuilderDies geschieht effizient, indem sichergestellt wird, dass der Basiswert []byteniemals leckt, und stringohne Kopie auf eine Weise konvertiert wird, die in Zukunft unterstützt wird. Dies gab es 2012 nicht. Die unten stehende Lösung von @ dimchansky ist seit Go 1.10 die richtige. Bitte überlegen Sie sich eine Bearbeitung!
Nuno Cruces

102

Die Antworten haben bisher nicht den "gesamten Stream" -Teil der Frage angesprochen. Ich denke, der gute Weg, dies zu tun, ist ioutil.ReadAll. Mit deinem io.ReaderCloserNamen rcwürde ich schreiben,

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

2
Danke, gute Antwort. Es sieht so aus, als würde buf.ReadFrom()auch der gesamte Stream bis zu EOF gelesen.
DJD

8
Wie lustig: Ich habe gerade die Implementierung von gelesen ioutil.ReadAll()und es verpackt einfach a bytes.Buffer's ReadFrom. Und die String()Methode des Puffers ist ein einfaches Wrap-Around-Casting string- die beiden Ansätze sind also praktisch gleich!
DJD

1
Dies ist die beste und prägnanteste Lösung.
mk12

1
Ich habe das gemacht und es funktioniert ... beim ersten Mal. Aus irgendeinem Grund geben die nachfolgenden Lesevorgänge nach dem Lesen der Zeichenfolge eine leere Zeichenfolge zurück. Ich weiß noch nicht warum.
Aldo 'xoen' Giambelluca

1
@ Aldo'xoen'Giambelluca ReadAll verbraucht den Leser, so dass beim nächsten Anruf nichts mehr zu lesen ist.
DanneJ


5

Der effizienteste Weg wäre, immer []bytestatt zu verwenden string.

Im Fall , dass Sie Daten aus den empfangenen drucken io.ReadCloser, das fmtkann Paket handhaben []byte, aber es ist nicht effizient , da die fmtImplementierung intern konvertieren []bytezu string. Um diese Konvertierung zu vermeiden, können Sie die fmt.FormatterSchnittstelle für einen Typ wie implementieren type ByteSlice []byte.


Ist die Konvertierung von [] Byte zu String teuer? Ich nahm an, dass string ([] byte) das [] byte nicht wirklich kopierte, sondern nur die Slice-Elemente als eine Reihe von Runen interpretierte. Aus diesem Grund habe ich Buffer.String () week.golang.org/src/pkg/bytes/buffer.go?s=1787:1819#L37 vorgeschlagen . Ich denke, es wäre gut zu wissen, was passiert, wenn string ([] byte) aufgerufen wird.
Nate

4
Die Umstellung von []byteauf stringist relativ schnell, aber die Frage lautete "der effizienteste Weg". Derzeit weist die Go-Laufzeit stringbeim Konvertieren []bytein immer eine neue zu string. Der Grund dafür ist, dass der Compiler nicht weiß, wie er bestimmen soll, ob der []bytenach der Konvertierung geändert wird. Hier gibt es Raum für Compiler-Optimierungen.

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
}


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.