Equivalente Guava per IOUtils.toString (InputStream)


106

Apache Commons IO ha un simpatico metodo pratico IOUtils.toString () per leggere un InputStreamin una stringa.

Dato che sto cercando di allontanarmi da Apache Commons e da Guava : c'è un equivalente in Guava? Ho esaminato tutte le classi nel com.google.common.iopacchetto e non sono riuscito a trovare nulla di altrettanto semplice.

Modifica: comprendo e apprezzo i problemi con i set di caratteri. Accade solo che io sappia che tutte le mie fonti sono in ASCII (sì, ASCII, non ANSI ecc.), Quindi in questo caso, la codifica non è un problema per me.


2
Informazioni sui set di caratteri: è comunque utile per una libreria richiederti di specificare che sai con quale set di caratteri hai a che fare (ad esempio Charsets.US_ASCII) piuttosto che lasciarti dire "eh, qualunque set di caratteri immagino?" cosa che molte persone sembrano felici di fare. Soprattutto perché Java non utilizza un valore predefinito che abbia senso, come UTF-8.
ColinD

Lo so. Ecco perché sto usando UTF-8 come versione predefinita nella mia risposta.
Sean Patrick Floyd


@Vadzim quei documenti non esistevano quando è stata posta questa domanda :-)
Sean Patrick Floyd

Risposte:


85

Hai dichiarato nel tuo commento alla risposta di Calum che avresti usato

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

Questo codice è problematico perché il sovraccarico CharStreams.toString(Readable)afferma:

Non chiude il file Readable.

Ciò significa che il tuo InputStreamReader, e per estensione il InputStreamrestituito da supplier.get(), non verrà chiuso al termine di questo codice.

Se invece approfitti del fatto che sembra che tu abbia già un InputSupplier<InputStream>e hai utilizzato l'overload CharStreams.toString(InputSupplier<R extends Readable & Closeable>), il toStringmetodo gestirà sia la creazione che la chiusura del Readerper te.

Questo è esattamente ciò che ha suggerito Jon Skeet, tranne per il fatto che in realtà non c'è alcun sovraccarico CharStreams.newReaderSupplierche prende un InputStreaminput ... devi dargli 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);

Il punto InputSupplierè semplificarti la vita consentendo a Guava di gestire le parti che richiedono un brutto try-finallyblocco per garantire che le risorse siano chiuse correttamente.

Modifica: personalmente, trovo quanto segue (che è come lo scriverei effettivamente, stavo solo analizzando i passaggi nel codice sopra)

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

essere molto meno prolisso di questo:

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);
}

Che è più o meno quello che dovresti scrivere per gestirlo correttamente da solo.


Modifica: febbraio 2014

InputSuppliere OutputSupplieri metodi che li utilizzano sono stati deprecati in Guava 16.0. I loro sostituti sono ByteSource, CharSource, ByteSinke CharSink. Dato un ByteSource, ora puoi ottenere il suo contenuto in Stringquesto modo:

ByteSource source = ...
String text = source.asCharSource(Charsets.UTF_8).read();

Grazie per le ottime informazioni (+1). Ma questo è molto prolisso. Penso che combinare la risposta accettata con Closeables.closeQuietly () sia più facile.
Sean Patrick Floyd

@CollinD: ho usato il tuo metodo in una delle mie risposte, dai un'occhiata al codice e dimmi se questo è il modo giusto per usare InputSupplier.
Emil

1
@ColinD, se inputStream proviene dall'interno di un servlet doPost, ha senso chiuderlo? (o preoccuparsi di chiuderlo)
Blankman

CharStreams.toString (InputSupplier) è ora deprecato. Ho creato un CharSource (da un ByteSource usando asCharSource) quindi ho usato il suo toString come suggeriscono i documenti.
John Lehmann

4
@ TedM.Young: Se tutto ciò che hai è un InputStream, e vuoi ottenerlo come un String, CharStreams.toString(new InputStreamReader(inputStream, charset))è la strada da percorrere. ByteSourcee CharSourcesono specifici per i casi in cui hai qualcosa che può fungere da fonte di InputStreams o Readers.
ColinD

56

Se hai un file Readablepuoi usare CharStreams.toString(Readable). Quindi probabilmente puoi fare quanto segue:

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

Ti obbliga a specificare un set di caratteri, cosa che immagino dovresti fare comunque.


4
In realtà, userò una combinazione delle risposte tue e di Jon Skeet: `CharStreams.toString (new InputStreamReader (supplier.get (), Charsets.UTF_8))"
Sean Patrick Floyd

Sì, molti modi per combinare le opzioni!
Calum

10
@SPFloyd: Se hai un, InputSupplier<InputStream>ti consiglio vivamente di usare CharStreams.newReaderSupplier(supplier, Charsets.UTF_8)piuttosto che new InputStreamReader. La ragione è che quando data la InputStreamReader, toStringsarà non vicino che Reader(e quindi non il flusso sottostante!). Usando un InputSupplierper il Reader, il toStringmetodo gestirà la chiusura del Readerper te.
ColinD

17

AGGIORNAMENTO : Guardando indietro, non mi piace la mia vecchia soluzione. Inoltre, è il 2013 e ora ci sono alternative migliori disponibili per Java7. Quindi ecco cosa uso ora:

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

o se con InputSupplier

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

16

Quasi. Potresti usare qualcosa del genere:

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

Personalmente non penso che IOUtils.toString(InputStream)sia "carino", perché usa sempre la codifica predefinita della piattaforma, che non è quasi mai quella che vuoi. C'è un sovraccarico che prende il nome della codifica, ma usare i nomi non è una grande idea IMO. Ecco perché mi piace Charsets.*.

EDIT: Non che quanto sopra ha bisogno di un InputSupplier<InputStream>come streamSupplier. Se hai già lo stream, puoi implementarlo abbastanza facilmente però:

InputSupplier<InputStream> supplier = new InputSupplier<InputStream>() {
    @Override public InputStream getInput() {
        return stream;
    }
};

Jon, è in streaming tramite request.getInputStream? Inoltre, il tuo chiuderà il flusso come ColinD menzionato nella risposta di @ Calum?
Blankman

Oh, ed è un ambiente doPost servlet, dovrei comunque chiudere il flusso?
Blankman

@Blankman: Ah, quindi questo è il tuo contesto - non era affatto chiaro dalla tua domanda. Non importa se chiudi un flusso di richieste, ma generalmente lo farei. Modificherò questa risposta però - non c'è un tale sovraccarico, a quanto pare.
Jon Skeet

1
Sto solo facendo questo ora: String payLoad = CharStreams.toString (new InputStreamReader (request.getInputStream (), "UTF-8"));
Blankman

1
@ BeeOnRope: immagino che un approccio intermedio sia Charsets.UTF_8.name(): più resistente agli errori di battitura.
Jon Skeet

11

Un'altra opzione è leggere i byte da Stream e creare una stringa da essi:

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

Non è Guava "puro", ma è un po 'più corto.


Purtroppo, ByteStreams.toByteArray()non chiude il flusso, secondo il Javadoc.
The Alchemist

È vero. Non ho visto alcuna funzione di Guava che chiude il flusso. Bene, tranne chiudi Tranquillamente.
ponomandr

1
In genere, lo stream viene aperto nell'istruzione try-with-resources e chiuso automaticamente, quindi non dovrebbe essere responsabilità di toByteArray ()
ponomandr

4

Sulla base della risposta accettata, ecco un metodo di utilità che deride il comportamento di IOUtils.toString()(e anche una versione sovraccarica con un set di caratteri). Questa versione dovrebbe essere sicura, giusto?

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);
    }
}

A me sembra più che soddisfacente. Le cose di Guava IO funzionano meglio se impari a pensare in termini di fornitori di input riutilizzabili piuttosto che di flussi e lettori 1-shot (quando possibile), ma immagino che dal momento che stai convertendo il codice IOUtils esistente questo sarebbe un grande cambiamento.
ColinD

2
Nel mio guava 14, il closeQuietly è già deprecato. Il suggerimento è di utilizzare la funzione di prova con le risorse che esiste in Java 7. Maggiori informazioni su questo su code.google.com/p/guava-libraries/wiki/…
bertie

2
@AlbertKam è d'accordo. Ma ricorda: questa risposta ha tre anni.
Sean Patrick Floyd

@SeanPatrickFloyd: Grazie! In realtà sono arrivato alla soluzione più recente partendo dalla tua risposta. Stavo pensando di aggiungere il commento per gli altri che potrebbero utilizzare la versione più recente. :)
bertie

4

Esiste una soluzione di chiusura automatica molto più breve nel caso in cui il flusso di input provenga dalla risorsa del percorso di classe:

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

Utilizza risorse Guava , ispirate da IOExplained .


1
La classe Resources non esisteva quando è stata posta questa domanda, ma hai ragione: oggi quella sarebbe probabilmente la strada da percorrere. Grazie
Sean Patrick Floyd

2

EDIT (2015): Okio è la migliore astrazione e strumenti per I / O in Java / Android che io conosca. Io lo uso per tutto il tempo.

FWIW ecco cosa uso.

Se ho già uno stream in mano, allora:

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));

Se sto creando uno stream:

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

Come esempio concreto, posso leggere un file di testo Android come questo:

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));

Tutto deprecato ora. :(
user3562927

1
Prova invece github.com/square/okio - Non ho usato l'I / O di Guava da un po 'di tempo, Okio è semplicemente migliore,
orip

0

Per un esempio concreto, ecco come posso leggere una risorsa di file di testo 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;
}
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.