Da io.Reader a stringa in Go


129

Ho un io.ReadCloseroggetto (da un http.Responseoggetto).

Qual è il modo più efficiente per convertire l'intero flusso in un stringoggetto?

Risposte:


175

MODIFICARE:

Dall'1.10, stringhe.Builder esiste. Esempio:

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

INFORMAZIONI SUCCESSIVE SOTTO

La risposta breve è che non sarà efficiente perché la conversione in una stringa richiede una copia completa dell'array di byte. Ecco il modo corretto (non efficiente) di fare ciò che vuoi:

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

Questa copia viene eseguita come meccanismo di protezione. Le stringhe sono immutabili. Se fosse possibile convertire un byte [] in una stringa, è possibile modificare il contenuto della stringa. Tuttavia, go consente di disabilitare i meccanismi di sicurezza del tipo utilizzando il pacchetto non sicuro. Utilizzare il pacchetto non sicuro a proprio rischio. Speriamo che solo il nome sia un avvertimento abbastanza buono. Ecco come lo farei usando non sicuro:

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

Eccoci, ora hai convertito in modo efficiente il tuo array di byte in una stringa. In realtà, tutto ciò che fa è ingannare il sistema dei tipi nel chiamarlo stringa. Ci sono un paio di avvertenze su questo metodo:

  1. Non ci sono garanzie che funzionerà in tutti i compilatori. Sebbene funzioni con il compilatore plan-9 gc, si basa su "dettagli di implementazione" non menzionati nelle specifiche ufficiali. Non si può nemmeno garantire che questo funzionerà su tutte le architetture o che non venga modificato in gc. In altre parole, questa è una cattiva idea.
  2. Quella stringa è mutabile! Se si effettuano chiamate su quel buffer, la stringa verrà modificata. Essere molto attenti.

Il mio consiglio è di attenersi al metodo ufficiale. Fare una copia non è così costoso e non vale i mali dei non sicuri. Se la stringa è troppo grande per fare una copia, non dovresti trasformarla in una stringa.


Grazie, questa è una risposta davvero dettagliata. Il modo "buono" sembra approssimativamente equivalente alla risposta di @ Sonia (dal momento che buf.String esegue il cast internamente).
DJ

1
E non funziona nemmeno con la mia versione, sembra non essere in grado di ottenere un puntatore da & but.Bytes (). Utilizzando Go1.
sinni800,

@ sinni800 Grazie per il suggerimento. Ho dimenticato che i ritorni di funzione non erano indirizzabili. Ora è stato risolto.
Stephen Weinberg,

3
Bene, i computer sono abbastanza dannatamente veloci nel copiare blocchi di byte. E dato che si tratta di una richiesta http, non riesco a immaginare uno scenario in cui la latenza di trasmissione non sarà di un miliardo di volte più grande del tempo banale impiegato per copiare l'array di byte. Qualsiasi linguaggio funzionale copia questo tipo di cose immutabili in tutto il luogo, e funziona ancora molto velocemente.
vedi più nitido il

Questa risposta non è aggiornata. strings.Builderlo fa in modo efficiente garantendo che il sottostante []bytenon perda mai e convertendosi stringsenza una copia in un modo che sarà supportato in futuro. Questo non esisteva nel 2012. La soluzione di @ dimchansky sotto è stata quella corretta da Go 1.10. Si prega di considerare una modifica!
Nuno Cruces,

102

Finora le risposte non hanno affrontato la parte "intero flusso" della domanda. Penso che il buon modo per farlo sia ioutil.ReadAll. Con il tuo io.ReaderClosernome rc, scriverei,

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

2
Grazie, buona risposta Sembra buf.ReadFrom()anche leggere l'intero flusso fino a EOF.
DJ

8
Che divertente: ho appena letto l'implementazione di ioutil.ReadAll()e semplicemente avvolge un bytes.Buffer's ReadFrom. E il String()metodo del buffer è un semplice giro attorno al casting string, quindi i due approcci sono praticamente gli stessi!
DJ

1
Questa è la soluzione migliore e più concisa.
mk12

1
L'ho fatto e funziona ... la prima volta. Per qualche motivo dopo aver letto la stringa, le letture del sequente restituiscono una stringa vuota. Non so ancora perché.
Aldo 'xoen' Giambelluca,

1
@ Aldo'xoen'Giambelluca ReadAll consuma il lettore, quindi alla prossima chiamata non c'è più niente da leggere.
DanneJ,

9
data, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(data))

5

Il modo più efficiente sarebbe usare sempre []byteinvece di string.

In caso di necessità di stampare i dati ricevuti dal io.ReadCloser, il fmtpacchetto in grado di gestire []byte, ma non è efficace perché l' fmtattuazione internamente la conversione []bytea string. Al fine di evitare questa conversione, è possibile implementare l' fmt.Formatterinterfaccia per un tipo simile type ByteSlice []byte.


La conversione da [] byte a stringa è costosa? Supponevo che la stringa ([] byte) non copiasse il byte [], ma interpretasse gli elementi slice come una serie di rune. Ecco perché ho suggerito Buffer.String () settimanale.golang.org/src/pkg/bytes/buffer.go?s=1787:1819#L37 . Immagino che sarebbe bene sapere cosa sta succedendo quando viene chiamato string ([] byte).
Nate

4
La conversione da []bytea stringè ragionevolmente veloce, ma la domanda era "il modo più efficiente". Attualmente, il runtime di Go assegnerà sempre un nuovo stringdurante la conversione []bytein string. La ragione di ciò è che il compilatore non sa come determinare se []byteverrà modificato dopo la conversione. C'è spazio per le ottimizzazioni del compilatore qui.

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
}


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.