Equivalente de guayaba para IOUtils.toString (InputStream)

106

Apache Commons IO tiene un método de conveniencia agradable IOUtils.toString () para leer InputStreamuna cadena.

Ya que estoy tratando de alejarme de Apache Commons y pasar a Guava : ¿hay un equivalente en Guava? Miré todas las clases en el com.google.common.iopaquete y no pude encontrar nada tan simple.

Editar: Entiendo y aprecio los problemas con los juegos de caracteres. Da la casualidad de que sé que todas mis fuentes están en ASCII (sí, ASCII, no ANSI, etc.), así que en este caso, la codificación no es un problema para mí.

Sean Patrick Floyd
fuente
2
Acerca de los juegos de caracteres: sigue siendo bueno que una biblioteca le solicite que especifique que sabe con qué juego de caracteres está tratando (por ejemplo Charsets.US_ASCII) en lugar de permitirle decir "eh, ¿cuál es el juego de caracteres, supongo?" que para mucha gente parece feliz de hacer. Especialmente porque Java no usa un valor predeterminado que tenga sentido, como UTF-8.
ColinD
Lo sé. Es por eso que estoy usando UTF-8 como versión predeterminada en mi propia respuesta.
Sean Patrick Floyd
Consulte también los documentos: code.google.com/p/guava-libraries/wiki/IOExplained
Vadzim
@Vadzim esos documentos no existían cuando se hizo esta pregunta :-)
Sean Patrick Floyd

Respuestas:

85

Dijiste en tu comentario sobre la respuesta de Calum que ibas a usar

CharStreams.toString(new InputStreamReader(supplier.get(), Charsets.UTF_8))

Este código es problemático porque los CharStreams.toString(Readable)estados de sobrecarga :

No cierra el Readable.

Esto significa que su InputStreamReader, y por extensión el InputStreamdevuelto por supplier.get(), no se cerrará después de que se complete este código.

Si, por otro lado, aprovecha el hecho de que parece que ya tiene un InputSupplier<InputStream>y usó la sobrecarga CharStreams.toString(InputSupplier<R extends Readable & Closeable>), el toStringmétodo manejará tanto la creación como el cierre del mismo Readerpor usted.

Esto es exactamente lo que sugirió Jon Skeet, excepto que en realidad no hay ninguna sobrecarga CharStreams.newReaderSupplierque tome InputStreamcomo entrada ... tienes que darle un InputSupplier:

InputSupplier<? extends InputStream> supplier = ...
InputSupplier<InputStreamReader> readerSupplier = 
    CharStreams.newReaderSupplier(supplier, Charsets.UTF_8);

// InputStream and Reader are both created and closed in this single call
String text = CharStreams.toString(readerSupplier);

El objetivo InputSupplieres hacer su vida más fácil permitiendo que Guava maneje las partes que requieren un try-finallybloque feo para asegurarse de que los recursos se cierren correctamente.

Editar: Personalmente, encuentro lo siguiente (que es como lo escribiría realmente, simplemente estaba desglosando los pasos en el código anterior)

String text = CharStreams.toString(
    CharStreams.newReaderSupplier(supplier, Charsets.UTF_8));

ser mucho menos detallado que esto:

String text;
InputStreamReader reader = new InputStreamReader(supplier.get(), 
    Charsets.UTF_8);
boolean threw = true;
try {
  text = CharStreams.toString(reader);
  threw = false;
}
finally {
  Closeables.close(reader, threw);
}

Que es más o menos lo que tendría que escribir para manejar esto correctamente usted mismo.


Edición: febrero de 2014

InputSuppliery OutputSupplierlos métodos que los utilizan han quedado obsoletos en Guava 16.0. Sus sustitutos son ByteSource, CharSource, ByteSinky CharSink. Dado un ByteSource, ahora puede obtener su contenido Stringasí:

ByteSource source = ...
String text = source.asCharSource(Charsets.UTF_8).read();
ColinD
fuente
Gracias por la excelente información (+1). Pero esto es muy detallado. Creo que combinar la respuesta aceptada con Closeables.closeQuietly () es más fácil.
Sean Patrick Floyd
@CollinD: He usado su método en una de mis respuestas. Por favor, mire el código y dígame si esta es la forma correcta de usar InputSupplier.
Emil
1
@ColinD, si inputStream proviene del interior de un servlet doPost, ¿tiene algún sentido cerrarlo? (o preocuparse por cerrarlo)
Blankman
CharStreams.toString (InputSupplier) ahora está en desuso. Creé un CharSource (desde un ByteSource usando asCharSource) y luego usé su toString como sugieren los documentos.
John Lehmann
4
@ TedM.Young: Si todo lo que tienes es un InputStream, y quieres obtenerlo como tal String, CharStreams.toString(new InputStreamReader(inputStream, charset))es el camino a seguir. ByteSourcey CharSourceson específicamente para casos en los que tiene algo que puede actuar como fuente de InputStreams o Readers.
ColinD
56

Si tienes un Readablepuedes usar CharStreams.toString(Readable). Entonces probablemente puedas hacer lo siguiente:

String string = CharStreams.toString( new InputStreamReader( inputStream, "UTF-8" ) );

Te obliga a especificar un conjunto de caracteres, lo que supongo que deberías estar haciendo de todos modos.

Calum
fuente
4
En realidad, usaré una combinación de tus respuestas y las de Jon Skeet: `CharStreams.toString (new InputStreamReader (provider.get (), Charsets.UTF_8))`
Sean Patrick Floyd
¡Sí, muchas formas de combinar las opciones!
Calum
10
@SPFloyd: Si tiene un InputSupplier<InputStream>, le recomiendo usar en CharStreams.newReaderSupplier(supplier, Charsets.UTF_8)lugar de new InputStreamReader. La razón es que cuando se le da la InputStreamReader, toStringlo hará no cerca que Reader(y por lo tanto no la corriente subyacente!). Al usar un InputSupplierpara el Reader, el toStringmétodo manejará el cierre del Readerpara usted.
ColinD
17

ACTUALIZACIÓN : Mirando hacia atrás, no me gusta mi solución anterior. Además, estamos en 2013 ahora y hay mejores alternativas disponibles ahora para Java7. Así que esto es lo que uso ahora:

InputStream fis = ...;
String text;
try (  InputStreamReader reader = new InputStreamReader(fis, Charsets.UTF_8)){
        text = CharStreams.toString(reader);
}

o si con InputSupplier

InputSupplier<InputStreamReader> spl = ...
try (  InputStreamReader reader = spl.getInput()){
        text = CharStreams.toString(reader);
    }
husayt
fuente
16

Casi. Podrías usar algo como esto:

InputSupplier<InputStreamReader> readerSupplier = CharStreams.newReaderSupplier
    (streamSupplier, Charsets.UTF_8);
String text = CharStreams.toString(readerSupplier);

Personalmente , no creo que IOUtils.toString(InputStream)sea ​​"agradable", porque siempre utiliza la codificación predeterminada de la plataforma, que casi nunca es la que quieres. Hay una sobrecarga que toma el nombre de la codificación, pero el uso de nombres no es una gran idea, en mi opinión. Por eso me gusta Charsets.*.

EDITAR: No es que lo anterior necesite un InputSupplier<InputStream>archivo streamSupplier. Sin embargo, si ya tiene la transmisión, puede implementarla con bastante facilidad:

InputSupplier<InputStream> supplier = new InputSupplier<InputStream>() {
    @Override public InputStream getInput() {
        return stream;
    }
};
Jon Skeet
fuente
Jon, ¿se transmite a través de request.getInputStream? Además, ¿el suyo cerrará la transmisión como ColinD mencionó en la respuesta de @ Calum?
Blankman
Ah, y es un entorno de servlet doPost, ¿debería cerrar la transmisión de todos modos?
Blankman
@Blankman: Ah, ese es su contexto, no quedó nada claro a partir de su pregunta. No importa demasiado si cierra un flujo de solicitud, pero generalmente lo hago. Sin embargo, editaré esta respuesta; parece que no hay tal sobrecarga.
Jon Skeet
1
Solo estoy haciendo esto ahora: String payLoad = CharStreams.toString (new InputStreamReader (request.getInputStream (), "UTF-8"));
Blankman
1
@BeeOnRope: Supongo que un enfoque intermedio es Charsets.UTF_8.name(): más resistente a errores tipográficos.
Jon Skeet
11

Otra opción es leer bytes de Stream y crear una cadena a partir de ellos:

new String(ByteStreams.toByteArray(inputStream))
new String(ByteStreams.toByteArray(inputStream), Charsets.UTF_8)

No es guayaba 'pura', pero es un poco más corta.

ponomandr
fuente
Lamentablemente, ByteStreams.toByteArray()no cierra el arroyo, según el Javadoc.
The Alchemist
Es verdad. No he visto ninguna función de Guava que cierre la transmisión. Bueno, excepto closeQuietly.
ponomandr
1
Por lo general, la secuencia se abre en la instrucción try-with-resources y se cierra automáticamente, por lo que no debería ser responsabilidad de toByteArray ()
ponomandr
4

Según la respuesta aceptada, aquí hay un método de utilidad que se burla del comportamiento de IOUtils.toString()(y también una versión sobrecargada con un juego de caracteres). Esta versión debería ser segura, ¿verdad?

public static String toString(final InputStream is) throws IOException{
    return toString(is, Charsets.UTF_8);
}


public static String toString(final InputStream is, final Charset cs)
throws IOException{
    Closeable closeMe = is;
    try{
        final InputStreamReader isr = new InputStreamReader(is, cs);
        closeMe = isr;
        return CharStreams.toString(isr);
    } finally{
        Closeables.closeQuietly(closeMe);
    }
}
Sean Patrick Floyd
fuente
Me parece bastante bien. Las cosas de IO de Guava funcionan mejor si aprendes a pensar en términos de proveedores de entrada reutilizables en lugar de transmisiones y lectores de 1 toma (cuando sea posible), pero supongo que, dado que estás convirtiendo el código IOUtils existente, eso sería un gran cambio.
ColinD
2
En mi guayaba 14, closeQuietly ya está en desuso. La sugerencia es usar la función try-with-resources que existe en Java 7. Más sobre esto en code.google.com/p/guava-libraries/wiki/…
bertie
2
@AlbertKam estuvo de acuerdo. Pero recuerde: esta respuesta tiene tres años.
Sean Patrick Floyd
@SeanPatrickFloyd: ¡Gracias! De hecho, llegué a la solución más nueva a partir de su respuesta. Estaba pensando en agregar el comentario para los demás que podrían estar usando la versión más nueva. :)
bertie
4

Hay una solución de cierre automático mucho más corta en caso de que el flujo de entrada provenga del recurso classpath:

URL resource = classLoader.getResource(path);
byte[] bytes = Resources.toByteArray(resource);
String text = Resources.toString(resource, StandardCharsets.UTF_8);

Utiliza Guava Resources , inspirado en IOExplained .

Vadzim
fuente
1
La clase de Recursos no existía cuando se hizo esta pregunta, pero tiene razón: hoy probablemente ese sea el camino a seguir. Gracias
Sean Patrick Floyd
2

EDITAR (2015): Okio es la mejor abstracción y herramientas para E / S en Java / Android que conozco. Lo uso todo el tiempo.

FWIW, esto es lo que uso.

Si ya tengo una transmisión en la mano, entonces:

final InputStream stream; // this is received from somewhere
String s = CharStreams.toString(CharStreams.newReaderSupplier(new InputSupplier<InputStream>() {
    public InputStream getInput() throws IOException {
        return stream;
    }
}, Charsets.UTF_8));

Si estoy creando una transmisión:

String s = CharStreams.toString(CharStreams.newReaderSupplier(new InputSupplier<InputStream>() {
    public InputStream getInput() throws IOException {
        return <expression creating the stream>;
    }
}, Charsets.UTF_8));

Como ejemplo concreto, puedo leer un activo de archivo de texto de Android como este:

final Context context = ...;
String s = CharStreams.toString(CharStreams.newReaderSupplier(new InputSupplier<InputStream>() {
    public InputStream getInput() throws IOException {
        return context.getAssets().open("my_asset.txt");
    }
}, Charsets.UTF_8));
orip
fuente
Todo en desuso ahora. :(
user3562927
1
Trato github.com/square/okio lugar - No he utilizado Guava E / S en un tiempo, Okio es simplemente mejor,
oriP
0

Para un ejemplo concreto, así es como puedo leer un activo de archivo de texto de Android:

public static String getAssetContent(Context context, String file) {
    InputStreamReader reader = null;
    InputStream stream = null;
    String output = "";

    try {
        stream = context.getAssets().open(file);
        reader = new InputStreamReader(stream, Charsets.UTF_8);
        output = CharStreams.toString(reader);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    return output;
}
TruMan1
fuente