De io.Reader para string em Go

129

Eu tenho um io.ReadCloserobjeto (de um http.Responseobjeto).

Qual é a maneira mais eficiente de converter todo o fluxo em um stringobjeto?

djd
fonte

Respostas:

175

EDITAR:

Desde a versão 1.10, existe o strings.Builder. Exemplo:

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

INFORMAÇÃO ATUALIZADA ABAIXO

A resposta curta é que não será eficiente, pois a conversão para uma string exige uma cópia completa da matriz de bytes. Aqui está a maneira correta (não eficiente) de fazer o que você deseja:

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

Esta cópia é feita como um mecanismo de proteção. Strings são imutáveis. Se você pudesse converter um byte [] em uma string, poderia alterar o conteúdo da string. No entanto, o go permite desativar os mecanismos de segurança de tipo usando o pacote não seguro. Use o pacote não seguro por sua conta e risco. Espero que o nome por si só seja um aviso suficientemente bom. Aqui está como eu faria isso usando inseguro:

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

Lá vamos nós, agora você converteu eficientemente sua matriz de bytes em uma string. Realmente, tudo o que isso faz é induzir o sistema de tipos a chamá-lo de string. Existem algumas ressalvas nesse método:

  1. Não há garantias de que isso funcionará em todos os compiladores go. Enquanto isso funciona com o compilador plan-9 gc, ele conta com "detalhes de implementação" não mencionados nas especificações oficiais. Você não pode nem garantir que isso funcione em todas as arquiteturas ou não seja alterado no gc. Em outras palavras, essa é uma péssima idéia.
  2. Essa string é mutável! Se você fizer alguma chamada nesse buffer, a string será alterada. Tenha muito cuidado.

Meu conselho é seguir o método oficial. Fazendo uma cópia não é que caro e não vale a pena os males do inseguro. Se a sequência for muito grande para fazer uma cópia, você não deve transformá-la em uma sequência.

Stephen Weinberg
fonte
Obrigado, essa é uma resposta realmente detalhada. A maneira "boa" parece aproximadamente equivalente à resposta de @ Sonia (já que o buf.String faz o elenco internamente).
DJD
1
E nem funciona com a minha versão, parece que não é possível obter um ponteiro de & but.Bytes (). Usando Go1.
precisa saber é o seguinte
@ sinni800 Obrigado pela dica. Esqueci que os retornos das funções não eram endereçáveis. Agora está consertado.
Stephen Weinberg
3
Bem, os computadores são muito rápidos em copiar blocos de bytes. E, como essa é uma solicitação http, não consigo imaginar um cenário em que a latência de transmissão não seja um milhão de vezes maior que o tempo trivial necessário para copiar a matriz de bytes. Qualquer linguagem funcional copia esse tipo de material imutável por todo o lado e ainda é executado muito rápido.
ver mais nítida
Esta resposta está desatualizada. strings.Builderfaz isso de maneira eficiente, garantindo que o subjacente []bytenunca vaze e convertendo para stringsem uma cópia de uma maneira que seja suportada no futuro. Isso não existia em 2012. A solução da @ dimchansky abaixo foi a correta desde o Go 1.10. Considere uma edição!
Nuno Cruces
102

Até agora, as respostas não abordaram a parte "stream inteiro" da pergunta. Eu acho que a boa maneira de fazer isso é ioutil.ReadAll. Com o seu io.ReaderClosernome rc, eu escreveria,

if b, err := ioutil.ReadAll(rc); err == nil {
    return string(b)
} ...
Sonia
fonte
2
Obrigado, boa resposta. Parece que buf.ReadFrom()também lê todo o fluxo até o EOF.
DJD
8
Como engraçado: Acabei de ler a implementação ioutil.ReadAll()e simplesmente envolve um bytes.Buffer's ReadFrom. E o String()método do buffer é uma quebra simples da conversão para string- portanto, as duas abordagens são praticamente as mesmas!
DJD
1
Essa é a melhor e mais concisa solução.
Mk12
1
Eu fiz isso e funciona ... pela primeira vez. Por algum motivo, depois de ler a sequência, as leituras seguintes retornam uma sequência vazia. Ainda não sei por que.
Aldo 'xoen' Giambelluca
1
@ Aldo'xoen'Giambelluca ReadAll consome o leitor, portanto, na próxima ligação, não há mais nada para ler.
DanneJ
9
data, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(data))
yakob abada
fonte
5

A maneira mais eficiente seria sempre usar em []bytevez de string.

Caso você precise imprimir dados recebidos do io.ReadCloser, o fmtpacote pode manipular []byte, mas não é eficiente porque a fmtimplementação será convertida internamente []byteem string. Para evitar essa conversão, você pode implementar a fmt.Formatterinterface para um tipo como type ByteSlice []byte.


fonte
A conversão de [] byte para string é cara? Eu assumi que a string ([] byte) não copiou o byte [], mas apenas interpretou os elementos da fatia como uma série de runas. Por isso, sugeri o Buffer.String () Weekly.golang.org/src/pkg/bytes/buffer.go?s=1787:1819#L37 . Eu acho que seria bom saber o que está acontecendo quando a string ([] byte) é chamada.
Nate
4
A conversão de []bytepara stringé razoavelmente rápida, mas a pergunta estava sendo feita "da maneira mais eficiente". Atualmente, o tempo de execução Go sempre aloca um novo stringao converter []bytepara string. A razão para isso é que o compilador não sabe como determinar se o []byteserá modificado após a conversão. Há espaço para otimizações do compilador aqui.
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
fonte
1
var b bytes.Buffer
b.ReadFrom(r)

// b.String()
Vojtech Vitek
fonte