De io.Reader a string en Go

129

Tengo un io.ReadCloserobjeto (de un http.Responseobjeto).

¿Cuál es la forma más eficiente de convertir toda la secuencia en un stringobjeto?

djd
fuente

Respuestas:

175

EDITAR:

Desde 1.10, strings.Builder existe. Ejemplo:

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

INFORMACIÓN ACTUALIZADA A CONTINUACIÓN

La respuesta corta es que no será eficiente porque la conversión a una cadena requiere hacer una copia completa de la matriz de bytes. Aquí está la forma adecuada (no eficiente) de hacer lo que quiere:

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

Esta copia se realiza como un mecanismo de protección. Las cuerdas son inmutables. Si pudiera convertir un byte [] en una cadena, podría cambiar el contenido de la cadena. Sin embargo, go le permite deshabilitar los mecanismos de seguridad de tipo utilizando el paquete inseguro. Utilice el paquete inseguro bajo su propio riesgo. Esperemos que solo el nombre sea una advertencia lo suficientemente buena. Así es como lo haría usando inseguro:

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

Ahí vamos, ahora ha convertido eficientemente su conjunto de bytes en una cadena. Realmente, todo lo que hace es engañar al sistema de tipos para que lo llame cadena. Hay un par de advertencias para este método:

  1. No hay garantías de que esto funcione en todos los compiladores. Si bien esto funciona con el compilador plan-9 gc, se basa en "detalles de implementación" no mencionados en las especificaciones oficiales. Ni siquiera puede garantizar que esto funcione en todas las arquitecturas o que no se cambie en gc. En otras palabras, esta es una mala idea.
  2. ¡Esa cuerda es mutable! Si realiza alguna llamada en ese búfer , cambiará la cadena. Ten mucho cuidado.

Mi consejo es seguir el método oficial. Hacer una copia no es que caro y no vale la pena los males de la insegura. Si la cadena es demasiado grande para hacer una copia, no debería convertirla en una cadena.

Stephen Weinberg
fuente
Gracias, esa es una respuesta muy detallada. La forma "buena" parece más o menos equivalente a la respuesta de @ Sonia también (ya que buf.String solo hace el reparto internamente).
djd
1
Y ni siquiera funciona con mi versión, parece que no puede obtener un puntero de & but.Bytes (). Usando Go1.
sinni800
@ sinni800 Gracias por la sugerencia. Olvidé que los retornos de funciones no eran direccionables. Ahora está arreglado.
Stephen Weinberg
3
Bueno, las computadoras son bastante rápidas al copiar bloques de bytes. Y dado que esta es una solicitud http, no puedo imaginar un escenario en el que la latencia de transmisión no sea un billón de veces mayor que el tiempo trivial que lleva copiar la matriz de bytes. Cualquier lenguaje funcional copia este tipo de cosas inmutables por todo el lugar, y aún funciona bastante rápido.
ver más agudo
Esta respuesta está desactualizada. strings.Builderhace esto de manera eficiente al garantizar que el subyacente []bytenunca se filtre y convertirlo stringsin una copia de una manera que sea compatible en el futuro. Esto no existía en 2012. La solución de @ dimchansky a continuación ha sido la correcta desde Go 1.10. Por favor considere una edición!
Nuno Cruces
102

Las respuestas hasta ahora no han abordado la parte "completa de la secuencia" de la pregunta. Creo que la buena manera de hacer esto es ioutil.ReadAll. Con tu io.ReaderClosernombre rc, escribiría

if b, err := ioutil.ReadAll(rc); err == nil {
    return string(b)
} ...
Sonia
fuente
2
Gracias, buena respuesta. Parece que buf.ReadFrom()también lee todo el flujo hasta EOF.
djd
8
Qué gracioso: acabo de leer la implementación de ioutil.ReadAll()y simplemente se ajusta un bytes.Buffer's ReadFrom. Y el String()método del búfer es una simple envoltura para transmitir, por stringlo que los dos enfoques son prácticamente iguales.
djd
1
Esta es la mejor y más concisa solución.
mk12
1
Hice esto y funciona ... la primera vez. Por alguna razón, después de leer la cadena, las lecturas posteriores devuelven una cadena vacía. No estoy seguro de por qué todavía.
Aldo 'xoen' Giambelluca
1
@ Aldo'xoen'Giambelluca ReadAll consume al lector, por lo que en la próxima llamada no queda nada para leer.
DanneJ
9
data, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(data))
yakob abada
fuente
5

La forma más eficiente sería usar siempre en []bytelugar de string.

En caso de tener que imprimir los datos recibidos desde el io.ReadCloser, el fmtpaquete puede manejar []byte, pero no es eficiente debido a que la fmtaplicación va a convertir internamente []bytea string. Para evitar esta conversión, puede implementar la fmt.Formatterinterfaz para un tipo como type ByteSlice []byte.


fuente
¿Es costosa la conversión de [] byte a cadena? Asumí que la cadena ([] byte) en realidad no copiaba el [] byte, sino que simplemente interpretaba los elementos del segmento como una serie de runas. Es por eso que sugerí Buffer.String () weekly.golang.org/src/pkg/bytes/buffer.go?s=1787:1819#L37 . Supongo que sería bueno saber qué sucede cuando se llama a la cadena ([] byte).
Nate
44
La conversión de []bytea stringes razonablemente rápida, pero la pregunta era "la forma más eficiente". Actualmente, el tiempo de ejecución de Go siempre asignará uno nuevo stringal convertir []bytea string. La razón de esto es que el compilador no sabe cómo determinar si []bytese modificará después de la conversión. Aquí hay espacio para optimizaciones del compilador.
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
fuente
1
var b bytes.Buffer
b.ReadFrom(r)

// b.String()
Vojtech Vitek
fuente