Odpowiednik guawy dla IOUtils.toString (InputStream)


106

Apache Commons IO ma wygodną metodę IOUtils.toString () do odczytywania ciągu znakówInputStream .

Ponieważ próbuję odejść od Apache Commons i przenieść się do Guawy : czy istnieje odpowiednik w guawie? Przejrzałem wszystkie klasy w com.google.common.iopakiecie i nie mogłem znaleźć nic tak prostego.

Edycja: rozumiem i doceniam problemy z zestawami znaków. Tak się składa, że ​​wiem, że wszystkie moje źródła są w ASCII (tak, ASCII, nie ANSI itp.), Więc w tym przypadku kodowanie nie jest dla mnie problemem.


2
O zestawach znaków: Wciąż dobrze jest, aby biblioteka wymagała od ciebie określenia, że ​​wiesz, z jakim zestawem znaków masz do czynienia (np. Charsets.US_ASCII), Zamiast pozwalać ci mówić "eh, jakikolwiek zgaduję?" co wielu ludziom wydaje się szczęśliwe. Zwłaszcza, że ​​Java nie używa wartości domyślnych, które mają sens, jak UTF-8.
ColinD

Wiem. Dlatego używam UTF-8 jako domyślnej wersji w mojej własnej odpowiedzi.
Sean Patrick Floyd


@Vadzim, te dokumenty nie istniały, gdy zadano to pytanie :-)
Sean Patrick Floyd.

Odpowiedzi:


85

W swoim komentarzu do odpowiedzi Caluma stwierdziłeś, że zamierzasz użyć

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

Ten kod jest problematyczny, ponieważ przeciążenie CharStreams.toString(Readable)stwierdza:

Nie zamyka Readable.

Oznacza to, że Twoje InputStreamReaderi przez rozszerzenie InputStreamzwrócone przez supplier.get()nie zostaną zamknięte po zakończeniu tego kodu.

Jeśli, z drugiej strony, wykorzystasz fakt, że wydaje się, że masz już InputSupplier<InputStream>przeciążenie i wykorzystałeś jeCharStreams.toString(InputSupplier<R extends Readable & Closeable> ), toStringmetoda zajmie się zarówno utworzeniem, jak i zamknięciemReader .

To jest dokładnie to, co zasugerował Jon Skeet, z wyjątkiem tego, że w rzeczywistości nie ma żadnego przeciążenia, CharStreams.newReaderSupplierktóre pobiera InputStreamjako dane wejściowe ... musisz podać mu 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);

Punkt InputSupplier to, aby ułatwić sobie życie, umożliwiając guawie obsługę części, które wymagają brzydkiego try-finallybloku, aby zapewnić prawidłowe zamknięcie zasobów.

Edycja: Osobiście uważam, że następujące (tak naprawdę to napisałbym, po prostu rozbijały kroki w powyższym kodzie)

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

być znacznie mniej szczegółowym niż to:

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

To mniej więcej tyle, ile trzeba by napisać, żeby samemu sobie z tym poradzić.


Edycja: luty 2014

InputSuppliera OutputSuppliermetody, które ich używają, zostały uznane za przestarzałe w Guava 16.0. Ich zamienniki są ByteSource, CharSource, ByteSinki CharSink. Biorąc pod uwagę ByteSource, możesz teraz pobrać jego zawartość w następujący sposób String:

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

Dzięki za świetne informacje (+1). Ale to jest bardzo rozwlekłe. Myślę, że połączenie zaakceptowanej odpowiedzi z Closeables.closeQuietly () jest łatwiejsze.
Sean Patrick Floyd

@CollinD: Użyłem Twojej metody w jednej z moich odpowiedzi. Spójrz na kod i powiedz mi, czy jest to właściwy sposób korzystania z InputSupplier.
Emil

1
@ColinD, jeśli inputStream pochodzi z wnętrza serwletu doPost, czy jest sens w zamykaniu go? (lub martwienie się o zamknięcie)
Blankman

CharStreams.toString (InputSupplier) jest teraz przestarzała. Utworzyłem CharSource (z ByteSource przy użyciu asCharSource), a następnie użyłem jego toString, jak sugeruje dokumentacja.
John Lehmann,

4
@ TedM.Young: Jeśli wszystko, co masz, to InputStreami chcesz uzyskać to jako a String, CharStreams.toString(new InputStreamReader(inputStream, charset))to jest droga. ByteSourcei CharSourcesą specjalnie przeznaczone do przypadków, w których masz coś, co może działać jako źródło InputStreams lub Readers.
ColinD,

56

Jeśli masz Readable, możesz użyć CharStreams.toString(Readable). Więc prawdopodobnie możesz wykonać następujące czynności:

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

Zmusza cię do określenia zestawu znaków, co chyba i tak powinieneś zrobić.


4
Właściwie użyję kombinacji odpowiedzi twojej i Jona Skeeta: `CharStreams.toString (new InputStreamReader (Supplier.get (), Charsets.UTF_8))`
Sean Patrick Floyd

Tak, wiele sposobów łączenia opcji!
Calum

10
@SPFloyd: Jeśli masz plik InputSupplier<InputStream>, zdecydowanie polecam używanie CharStreams.newReaderSupplier(supplier, Charsets.UTF_8)zamiast new InputStreamReader. Powodem jest to, że gdy biorąc pod uwagę InputStreamReader, toStringbędzie nie blisko, że Reader(a więc nie strumienia bazowego!). Używając InputSupplierfor the Reader, toStringmetoda zajmie się zamknięciem Reader.
ColinD

17

AKTUALIZACJA : Patrząc wstecz, nie podoba mi się moje stare rozwiązanie. Poza tym jest rok 2013 i są dostępne lepsze alternatywy dla Java7. Oto, czego teraz używam:

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

lub jeśli z InputSupplier

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

16

Prawie. Możesz użyć czegoś takiego:

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

Osobiście nie uważam, że IOUtils.toString(InputStream)jest to „miłe” - ponieważ zawsze używa domyślnego kodowania platformy, które prawie nigdy nie jest tym, czego chcesz. Istnieje przeciążenie, które przyjmuje nazwę kodowania, ale używanie nazw nie jest dobrym pomysłem IMO. Dlatego lubię Charsets.*.

EDIT: Nie, że powyższe należy przedsięwziąć InputSupplier<InputStream>jak streamSupplier. Jeśli masz już strumień, możesz to łatwo zaimplementować:

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

Jon, czy przesyłasz strumieniowo przez request.getInputStream? Ponadto, czy twój zamknie strumień, jak ColinD wspomniany w odpowiedzi @ Calum?
Blankman

Aha, i jest to środowisko doPost serwletu, czy powinienem i tak zamknąć strumień?
Blankman

@Blankman: Ach, więc taki jest twój kontekst - nie było to jasne z twojego pytania. Nie ma większego znaczenia, czy zamkniesz strumień żądań, ale generalnie bym to zrobił. Zmienię jednak tę odpowiedź - wydaje się, że nie ma takiego przeciążenia.
Jon Skeet

1
Robię to teraz: String payLoad = CharStreams.toString (new InputStreamReader (request.getInputStream (), "UTF-8"));
Blankman

1
@BeeOnRope: Wydaje mi się, że jedno pośrednie podejście jest Charsets.UTF_8.name()- bardziej odporne na typo.
Jon Skeet

11

Inną opcją jest odczytanie bajtów ze Stream i utworzenie z nich ciągu:

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

To nie jest „czysta” guawa, ale jest trochę krótsza.


Niestety ByteStreams.toByteArray()nie zamyka strumienia, zgodnie z Javadoc.
The Alchemist

To prawda. Nie widziałem żadnej funkcji Guavy, która zamyka strumień. Cóż, z wyjątkiem blisko Cicho.
ponomandr

1
Zazwyczaj strumień jest otwierany w instrukcji try-with-resources i zamykany automatycznie, więc nie powinien być odpowiedzialny za toByteArray ()
ponomandr

4

Opierając się na zaakceptowanej odpowiedzi, oto metoda narzędziowa, która kpi z zachowania IOUtils.toString()(a także przeciążonej wersji z zestawem znaków). Ta wersja powinna być bezpieczna, prawda?

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

Dla mnie wygląda całkiem nieźle. Rzeczy IO Guavy działają najlepiej, jeśli nauczysz się myśleć w kategoriach dostawców danych wejściowych wielokrotnego użytku, a nie pojedynczych strumieni i czytników (jeśli to możliwe), ale myślę, że skoro konwertujesz istniejący kod IOUtils, byłaby to duża zmiana.
ColinD

2
W mojej guawie 14 funkcja closeQuietly jest już przestarzała. Sugeruje się użycie funkcji próbowania z zasobami, która istnieje w Javie 7. Więcej na ten temat na code.google.com/p/guava-libraries/wiki/ ...
bertie

2
@AlbertKam się zgodził. Ale pamiętaj: ta odpowiedź ma trzy lata.
Sean Patrick Floyd

@SeanPatrickFloyd: Dzięki! Właściwie dotarłem do nowszego rozwiązania, zaczynając od Twojej odpowiedzi. Myślałem o dodaniu komentarza dla innych, którzy mogą używać nowszej wersji. :)
bertie

4

Istnieje znacznie krótsze rozwiązanie autozamykania w przypadku, gdy strumień wejściowy pochodzi z zasobu classpath:

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

Wykorzystuje zasoby guawy , zainspirowane IOExplained .


1
Klasa Resources nie istniała, kiedy zadano to pytanie, ale masz rację: dzisiaj prawdopodobnie byłaby to najlepsza droga. Dzięki
Sean Patrick Floyd

2

EDYCJA (2015): Okio to najlepsza abstrakcja i narzędzia do I / O w Javie / Androidzie, jakie znam. Używam go cały czas.

Oto czego używam FWIW.

Jeśli mam już strumień w ręku, to:

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

Jeśli tworzę strumień:

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

Jako konkretny przykład mogę odczytać zasób pliku tekstowego Androida w następujący sposób:

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

Wszystkie są teraz przestarzałe. :(
user3562927

1
Zamiast tego spróbuj github.com/square/okio - od jakiegoś czasu nie korzystałem z I / O Guavy, Okio jest po prostu lepsze
lubip

0

Aby uzyskać konkretny przykład, oto jak mogę odczytać zasób pliku tekstowego Androida:

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;
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.