Od io.Reader do stringów w Go

134

Mam io.ReadCloserobiekt (z http.Responseobiektu).

Jaki jest najbardziej efektywny sposób przekształcenia całego strumienia w stringobiekt?

djd
źródło

Odpowiedzi:

183

EDYTOWAĆ:

Od 1.10 istnieje strings.Builder. Przykład:

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

NIEAKTUALNE INFORMACJE PONIŻEJ

Krótka odpowiedź jest taka, że ​​nie będzie to efektywne, ponieważ konwersja na łańcuch wymaga wykonania pełnej kopii tablicy bajtów. Oto właściwy (nieefektywny) sposób robienia tego, co chcesz:

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

Ta kopia jest wykonywana jako mechanizm ochronny. Ciągi znaków są niezmienne. Gdybyś mógł przekonwertować [] bajt na łańcuch, mógłbyś zmienić zawartość łańcucha. Jednak go pozwala wyłączyć mechanizmy bezpieczeństwa typu przy użyciu niebezpiecznego pakietu. Używaj niebezpiecznego pakietu na własne ryzyko. Miejmy nadzieję, że sama nazwa jest wystarczająco dobrym ostrzeżeniem. Oto, jak zrobiłbym to, używając niebezpiecznego:

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

Proszę bardzo, teraz skutecznie przekonwertowałeś swoją tablicę bajtów na łańcuch. Naprawdę wszystko to oszukuje system typów, aby nazwał go łańcuchem. Istnieje kilka zastrzeżeń dotyczących tej metody:

  1. Nie ma gwarancji, że to zadziała we wszystkich kompilatorach go. Chociaż działa to z kompilatorem plan-9 gc, opiera się na „szczegółach implementacji” nie wymienionych w oficjalnej specyfikacji. Nie możesz nawet zagwarantować, że zadziała to na wszystkich architekturach lub nie zostanie zmienione w gc. Innymi słowy, to zły pomysł.
  2. Ten ciąg jest zmienny! Wprowadzenie jakichkolwiek rozmów na temat tego bufora to będzie zmienić ciąg. Bądź bardzo ostrożny.

Radzę trzymać się oficjalnej metody. Robi kopię nie jest to drogie i nie warto zło niebezpieczne. Jeśli ciąg jest zbyt duży, aby wykonać kopię, nie powinieneś tworzyć z niego łańcucha.

Stephen Weinberg
źródło
Dzięki, to bardzo szczegółowa odpowiedź. „Dobry” sposób wydaje się z grubsza równoważny również z odpowiedzią @ Sonii (ponieważ buf.String wykonuje tylko wewnętrzne rzutowanie).
djd
1
I to nawet nie działa z moją wersją, wydaje się, że nie jest w stanie uzyskać wskaźnika z & ale.Bytes (). Korzystanie z Go1.
sinni800
@ sinni800 Dzięki za wskazówkę. Zapomniałem, że zwroty funkcji nie były adresowalne. Jest teraz naprawiony.
Stephen Weinberg
3
Komputery są cholernie szybkie w kopiowaniu bloków bajtów. Biorąc pod uwagę, że jest to żądanie http, nie mogę sobie wyobrazić scenariusza, w którym opóźnienie transmisji nie byłoby nawet miliony razy większe niż trywialny czas potrzebny do skopiowania tablicy bajtów. Każdy język funkcjonalny kopiuje tego typu niezmienne rzeczy w każdym miejscu i nadal działa bardzo szybko.
zobacz ostrzejsze
Ta odpowiedź jest nieaktualna. strings.Builderrobi to skutecznie, upewniając się, że baza []bytenigdy nie przecieka i konwertuje do wersji stringbez kopii w sposób, który będzie obsługiwany w przyszłości. To nie istniało w 2012 roku. Poniższe rozwiązanie @ dimchansky jest poprawne od wersji Go 1.10. Rozważ zmianę!
Nuno Cruces
103

Dotychczasowe odpowiedzi nie dotyczyły części pytania dotyczącej „całego strumienia”. Myślę, że dobrym sposobem na to jest ioutil.ReadAll. Z twoim io.ReaderCloserimieniem rcnapisałbym,

if b, err := ioutil.ReadAll(rc); err == nil {
    return string(b)
} ...
Sonia
źródło
2
Dzięki, dobra odpowiedź. Wygląda na to, że buf.ReadFrom()odczytuje również cały strumień aż do EOF.
djd
9
Jak śmieszne: Właśnie przeczytałem wdrażania ioutil.ReadAll()i po prostu owija bytes.Buffer„s ReadFrom. A String()metoda bufora polega na prostym rzutowaniu dookoła string- więc te dwa podejścia są praktycznie takie same!
djd
1
To najlepsze, najbardziej zwięzłe rozwiązanie.
mk12
1
Zrobiłem to i działa ... za pierwszym razem. Z jakiegoś powodu po odczytaniu ciągu kolejne odczyty zwracają pusty ciąg. Jeszcze nie wiem, dlaczego.
Aldo 'xoen' Giambelluca
1
@ Aldo'xoen'Giambelluca ReadAll zużywa czytnik, więc w następnym połączeniu nie ma już nic do przeczytania.
DanneJ
10
data, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(data))
yakob abada
źródło
5

Najbardziej efektywnym sposobem byłoby zawsze używanie []bytezamiast string.

W przypadku, gdy musisz wydrukować dane otrzymane z io.ReadCloser, fmtpakiet może obsłużyć []byte, ale nie jest to wydajne, ponieważ fmtimplementacja zostanie wewnętrznie przekonwertowana []bytena string. Aby uniknąć tej konwersji, możesz zaimplementować fmt.Formatterinterfejs dla typu type ByteSlice []byte.


źródło
Czy konwersja z [] bajtu na ciąg znaków jest kosztowna? Założyłem, że string ([] bajt) w rzeczywistości nie kopiował [] bajtu, ale po prostu zinterpretował elementy plasterka jako serię run. Dlatego zasugerowałem Buffer.String () week.golang.org/src/pkg/bytes/buffer.go?s=1787:1819#L37 . Myślę, że dobrze byłoby wiedzieć, co się dzieje, gdy wywoływany jest łańcuch ([] bajt).
Nate
4
Konwersja z []bytena stringjest dość szybka, ale pytanie dotyczyło „najbardziej wydajnego sposobu”. Obecnie środowisko wykonawcze Go zawsze przydziela nowy stringpodczas konwersji []bytedo string. Powodem tego jest to, że kompilator nie wie, jak określić, czy []byteplik zostanie zmodyfikowany po konwersji. Jest tu miejsce na optymalizacje kompilatora.
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
}
Dimchansky
źródło
1
var b bytes.Buffer
b.ReadFrom(r)

// b.String()
Vojtech Vitek
źródło