Guavenäquivalent für IOUtils.toString (InputStream)

106

Apache Commons IO hat eine nette Komfortmethode IOUtils.toString () zum Lesen eines StringsInputStream .

Da ich versuche, mich von Apache Commons nach Guave zu entfernen: Gibt es in Guave ein Äquivalent? Ich habe mir alle Klassen im com.google.common.ioPaket angesehen und konnte nichts annähernd so Einfaches finden.

Bearbeiten: Ich verstehe und schätze die Probleme mit Zeichensätzen. Es kommt einfach so vor, dass ich weiß, dass alle meine Quellen in ASCII sind (ja, ASCII, nicht ANSI usw.). In diesem Fall ist die Codierung für mich kein Problem.

Sean Patrick Floyd
quelle
2
Zu den Zeichensätzen: Für eine Bibliothek ist es immer noch gut, wenn Sie angeben müssen, dass Sie wissen, mit welchem ​​Zeichensatz Sie es zu tun haben (z. B. Charsets.US_ASCII), anstatt Sie sagen zu lassen: "Äh, welcher Zeichensatz denke ich?" was allen zu gerne scheint. Zumal Java keine sinnvolle Standardeinstellung wie UTF-8 verwendet.
ColinD
Ich weiß. Deshalb verwende ich in meiner eigenen Antwort UTF-8 als Standardversion.
Sean Patrick Floyd
Siehe auch die Dokumente: code.google.com/p/guava-libraries/wiki/IOExplained
Vadzim
@ Vadzim diese Dokumente existierten nicht, als diese Frage gestellt wurde :-)
Sean Patrick Floyd

Antworten:

85

Sie haben in Ihrem Kommentar zu Calums Antwort angegeben, dass Sie sie verwenden würden

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

Dieser Code ist problematisch, da die Überlast CharStreams.toString(Readable)lautet:

Schließt das nicht Readable.

Dies bedeutet, dass Ihr InputStreamReaderund im weiteren Sinne das InputStreamzurückgegebene von supplier.get()nach Abschluss dieses Codes nicht geschlossen wird.

Wenn Sie andererseits die Tatsache ausnutzen, dass Sie anscheinend bereits eine haben InputSupplier<InputStream>und die Überladung verwendet haben CharStreams.toString(InputSupplier<R extends Readable & Closeable>, toStringübernimmt die Methode sowohl das Erstellen als auch das Schließen der Readerfür Sie.

Dies ist genau das, was Jon Skeet vorgeschlagen hat, außer dass es tatsächlich keine Überladung gibt CharStreams.newReaderSupplier, die InputStreamals Eingabe verwendet wird ... Sie müssen ihm Folgendes geben 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);

Ziel InputSupplierist es, Ihnen das Leben zu erleichtern, indem Sie Guava erlauben, mit den Teilen umzugehen, die einen hässlichen try-finallyBlock erfordern , um sicherzustellen, dass die Ressourcen ordnungsgemäß geschlossen werden.

Bearbeiten: Persönlich finde ich Folgendes (so würde ich es tatsächlich schreiben, habe nur die Schritte im obigen Code aufgeschlüsselt)

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

wird weit weniger ausführlich als das:

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

Welches ist mehr oder weniger das, was Sie schreiben müssten, um dies selbst richtig zu handhaben.


Bearbeiten: Februar 2014

InputSupplierund OutputSupplierund die Methoden, die sie verwenden, sind in Guava 16.0 veraltet. Deren Ersatz sind ByteSource, CharSource, ByteSinkund CharSink. Mit a ByteSourcekönnen Sie den Inhalt nun wie Stringfolgt abrufen:

ByteSource source = ...
String text = source.asCharSource(Charsets.UTF_8).read();
ColinD
quelle
Danke für die tollen Infos (+1). Das ist aber sehr ausführlich. Ich denke, dass es einfacher ist, die akzeptierte Antwort mit Closeables.closeQuietly () zu kombinieren.
Sean Patrick Floyd
@CollinD: Ich habe Ihre Methode in einer meiner Antworten verwendet. Bitte schauen Sie sich den Code an und sagen Sie mir, ob dies der richtige Weg ist, InputSupplier zu verwenden.
Emil
1
@ColinD, wenn der inputStream aus einem doPost-Servlet stammt, gibt es einen Grund, ihn zu schließen? (oder sich Sorgen machen, es zu schließen)
Blankman
CharStreams.toString (InputSupplier) ist jetzt veraltet. Ich habe eine CharSource (aus einer ByteSource mit asCharSource) erstellt und dann ihren toString verwendet, wie in den Dokumenten vorgeschlagen.
John Lehmann
4
@ TedM.Young: Wenn alles, was Sie haben, ein InputStreamist und Sie es als erhalten möchten String, CharStreams.toString(new InputStreamReader(inputStream, charset))ist der richtige Weg. ByteSourceund CharSourcesind speziell für Fälle, in denen Sie etwas haben, das als Quelle von InputStreams oder Readers dienen kann.
ColinD
56

Wenn Sie eine haben Readable, können Sie verwenden CharStreams.toString(Readable). Sie können also wahrscheinlich Folgendes tun:

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

Erzwingt die Angabe eines Zeichensatzes, was Sie wahrscheinlich trotzdem tun sollten.

Calum
quelle
4
Eigentlich werde ich eine Kombination aus Ihren und Jon Skeets Antworten verwenden: `CharStreams.toString (neuer InputStreamReader (vendor.get (), Charsets.UTF_8))`
Sean Patrick Floyd
Ja, viele Möglichkeiten, die Optionen zu kombinieren!
Calum
10
@ SPFloyd: Wenn Sie eine haben, InputSupplier<InputStream>würde ich dringend empfehlen, CharStreams.newReaderSupplier(supplier, Charsets.UTF_8)anstatt zu verwenden new InputStreamReader. Der Grund ist, dass, wenn das gegeben ist InputStreamReader, toStringdas nicht geschlossen wird Reader(und somit nicht der zugrunde liegende Stream!). Wenn Sie ein InputSupplierfor für verwenden Reader, toStringübernimmt die Methode das Schließen des Readerfor für Sie.
ColinD
17

UPDATE : Rückblickend mag ich meine alte Lösung nicht. Außerdem ist es jetzt 2013 und es gibt jetzt bessere Alternativen für Java7. Also hier ist was ich jetzt benutze:

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

oder wenn mit InputSupplier

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

Fast. Sie könnten so etwas verwenden:

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

Persönlich finde ich das nichtIOUtils.toString(InputStream) "nett" - weil es immer die Standardcodierung der Plattform verwendet, was fast nie das ist, was Sie wollen. Es gibt eine Überladung, die den Namen der Codierung annimmt, aber die Verwendung von Namen ist IMO keine gute Idee. Deshalb mag ich Charsets.*.

EDIT: Nicht, dass das oben genannte ein InputSupplier<InputStream>wie das braucht streamSupplier. Wenn Sie den Stream bereits haben, können Sie dies jedoch leicht genug implementieren:

InputSupplier<InputStream> supplier = new InputSupplier<InputStream>() {
    @Override public InputStream getInput() {
        return stream;
    }
};
Jon Skeet
quelle
Jon, ist Stream über request.getInputStream? Werden Sie den Stream schließen, wie ColinD in der Antwort von @ Calum erwähnt hat?
Blankman
Oh, und es ist eine Servlet-doPost-Umgebung, sollte ich den Stream trotzdem schließen?
Blankman
@Blankman: Ah, das ist also dein Kontext - es war aus deiner Frage überhaupt nicht klar. Es spielt keine Rolle, ob Sie einen Anforderungsdatenstrom schließen, aber ich würde dies im Allgemeinen tun. Ich werde diese Antwort jedoch bearbeiten - es scheint keine solche Überlastung zu geben.
Jon Skeet
1
Ich mache das gerade: String payLoad = CharStreams.toString (neuer InputStreamReader (request.getInputStream (), "UTF-8"));
Blankman
1
@BeeOnRope: Ich denke, ein Zwischenansatz ist Charsets.UTF_8.name()- typo-resistenter.
Jon Skeet
11

Eine andere Möglichkeit besteht darin, Bytes aus Stream zu lesen und daraus einen String zu erstellen:

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

Es ist keine "reine" Guave, aber etwas kürzer.

ponomandr
quelle
Leider ByteStreams.toByteArray()schließt der Stream laut Javadoc nicht.
Der Alchemist
Das ist richtig. Ich habe keine Guava-Funktion gesehen, die den Stream schließt. Nun, außer in der Nähe.
Ponomandr
1
In der Regel wird der Stream in der Anweisung "try-with-resources" geöffnet und automatisch geschlossen, sodass toByteArray ()
ponomandr
4

Basierend auf der akzeptierten Antwort ist hier eine Dienstprogrammmethode, die das Verhalten von IOUtils.toString()(und eine überladene Version mit einem Zeichensatz) verspottet . Diese Version sollte sicher sein, oder?

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
quelle
Sieht für mich ziemlich gut aus. Guavas IO-Zeug funktioniert am besten, wenn Sie lernen, in wiederverwendbaren Eingabeanbietern zu denken, anstatt in 1-Shot-Streams und -Lesern (wenn möglich), aber ich denke, da Sie vorhandenen IOUtils-Code konvertieren, wäre dies eine große Änderung.
ColinD
2
In meiner Guave 14 ist die closeQuietly bereits veraltet. Der Vorschlag ist, die Try-with-Resources-Funktion zu verwenden, die in Java 7 vorhanden ist. Mehr dazu unter code.google.com/p/guava-libraries/wiki/…
Bertie
2
@ AlbertKam stimmte zu. Aber denken Sie daran: Diese Antwort ist drei Jahre alt.
Sean Patrick Floyd
@ SeanPatrickFloyd: Danke! Eigentlich bin ich von Ihrer Antwort zu der neueren Lösung gekommen. Ich dachte daran, den Kommentar für die anderen hinzuzufügen, die möglicherweise die neuere Version verwenden. :) :)
Bertie
4

Es gibt eine viel kürzere Autoclosing-Lösung für den Fall, dass der Eingabestream von einer Klassenpfadressource stammt:

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

Verwendet Guave Ressourcen , inspiriert von IOExplained .

Vadzim
quelle
1
Die Resources-Klasse gab es nicht, als diese Frage gestellt wurde, aber Sie haben Recht: Heute wäre das wahrscheinlich der richtige Weg. Vielen Dank
Sean Patrick Floyd
2

EDIT (2015): Okio ist die beste Abstraktion und die besten Tools für E / A in Java / Android, die ich kenne. Ich benutze es die ganze Zeit.

FWIW hier ist was ich benutze.

Wenn ich bereits einen Stream in der Hand habe, dann:

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

Wenn ich einen Stream erstelle:

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

Als konkretes Beispiel kann ich ein Android-Textdatei-Asset wie folgt lesen:

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
quelle
Alles jetzt veraltet. :(
user3562927
1
Versuchen Sie stattdessen github.com/square/okio - ich habe Guavas E / A schon eine Weile nicht mehr verwendet, Okio ist einfach besser
orip
0

Ein konkretes Beispiel ist, wie ich ein Android-Textdatei-Asset lesen kann:

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
quelle