Эквивалент Guava для IOUtils.toString (InputStream)

106

Apache Commons IO имеет удобный удобный метод IOUtils.toString () для чтения InputStreamстроки в строку.

Поскольку я пытаюсь перейти от Apache Commons к Guava : есть ли эквивалент в Guava? Я просмотрел все классы в com.google.common.ioпакете и не нашел ничего более простого.

Изменить: я понимаю и ценю проблемы с кодировками. Так уж случилось, что я знаю, что все мои источники находятся в ASCII (да, ASCII, а не ANSI и т. Д.), Поэтому в этом случае кодировка для меня не проблема.

Шон Патрик Флойд
источник
2
О кодировках: для библиотеки по-прежнему хорошо требовать, чтобы вы указали, что вы знаете, с какой кодировкой имеете дело (например Charsets.US_ASCII), вместо того, чтобы позволять вам говорить «а, какую кодировку я думаю?» что многим кажется счастливым делать. Тем более, что Java не использует значение по умолчанию, которое имеет смысл, например UTF-8.
ColinD
Я знаю. Вот почему я использую UTF-8 по умолчанию в своем ответе.
Шон Патрик Флойд
См. Также документы: code.google.com/p/guava-libraries/wiki/IOExplained
Vadzim
@Vadzim, эти документы не существовали, когда был задан этот вопрос :-)
Шон Патрик Флойд

Ответы:

85

В своем комментарии к ответу Калума вы заявили, что собираетесь использовать

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

Этот код проблематичен, поскольку в перегрузке CharStreams.toString(Readable)указано:

Не закрывает Readable.

Это означает, что ваш InputStreamReaderи, соответственно, InputStreamвозвращаемый supplier.get(), не будут закрыты после завершения этого кода.

Если, с другой стороны, вы воспользуетесь тем фактом, что у вас уже есть InputSupplier<InputStream>и используется перегрузка CharStreams.toString(InputSupplier<R extends Readable & Closeable>), toStringметод будет обрабатывать как создание, так и закрытие Readerдля вас.

Это именно то, что предложил Джон Скит, за исключением того, что на самом деле нет никакой перегрузки, CharStreams.newReaderSupplierкоторая принимает в InputStreamкачестве входных данных ... вы должны дать ему 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);

Смысл в InputSupplierтом, чтобы упростить вашу жизнь, позволив Guava обрабатывать части, требующие уродливого try-finallyблока, чтобы гарантировать правильное закрытие ресурсов.

Изменить: Лично я нахожу следующее (именно так я бы написал это, просто нарушил шаги в приведенном выше коде)

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

быть гораздо менее подробным, чем это:

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

Это более или менее то, что вам нужно было бы написать, чтобы справиться с этим самостоятельно.


Изменить: февраль 2014 г.

InputSupplierи OutputSupplierметоды, которые их используют, устарели в Guava 16.0. Их замены являются ByteSource, CharSource, ByteSinkи CharSink. Учитывая a ByteSource, теперь вы можете получить его содержимое Stringследующим образом:

ByteSource source = ...
String text = source.asCharSource(Charsets.UTF_8).read();
ColinD
источник
Спасибо за отличную информацию (+1). Но это очень многословно. Я думаю, что объединить принятый ответ с Closeables.closeQuietly () проще.
Шон Патрик Флойд
@CollinD: Я использовал ваш метод в одном из своих ответов. Пожалуйста, взгляните на код и скажите мне, правильный ли это способ использования InputSupplier.
Emil
1
@ColinD, если inputStream исходит изнутри сервлета doPost, есть ли смысл его закрывать? (или беспокоясь о его закрытии)
Бланкман
CharStreams.toString (InputSupplier) теперь устарел. Я создал CharSource (из ByteSource с использованием asCharSource), а затем использовал его toString, как предлагают документы.
Джон Леманн
4
@ TedM.Young: Если все, что у вас есть InputStream, и вы хотите получить его в качестве String, CharStreams.toString(new InputStreamReader(inputStream, charset))это лучший вариант . ByteSourceи CharSourceпредназначены специально для случаев, когда у вас есть что-то, что может выступать в качестве источника InputStreams или Readers.
ColinD
56

Если у вас есть, Readableвы можете использовать CharStreams.toString(Readable). Итак, вы, вероятно, можете сделать следующее:

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

Заставляет вас указать набор символов, что, я думаю, вам все равно следует делать.

Calum
источник
4
На самом деле, я буду использовать комбинацию ваших ответов и ответов Джона Скита: `CharStreams.toString (new InputStreamReader (supplier.get (), Charsets.UTF_8))`
Шон Патрик Флойд
Да, есть множество способов комбинировать варианты!
Calum
10
@SPFloyd: Если у вас есть, InputSupplier<InputStream>я настоятельно рекомендую использовать CharStreams.newReaderSupplier(supplier, Charsets.UTF_8)вместо new InputStreamReader. Причина заключается в том, что , когда , учитывая InputStreamReader, toStringбудет не близко , что Reader(и , следовательно , не основной поток!). Используя InputSupplierдля Reader, toStringметод выполнит закрытие Readerза вас.
ColinD
17

ОБНОВЛЕНИЕ : оглядываясь назад, мне не нравится мое старое решение. Кроме того, сейчас 2013 год, и теперь для Java7 доступны лучшие альтернативы. Итак, вот что я использую сейчас:

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

или если с InputSupplier

InputSupplier<InputStreamReader> spl = ...
try (  InputStreamReader reader = spl.getInput()){
        text = CharStreams.toString(reader);
    }
Хусайт
источник
16

Почти. Вы можете использовать что-то вроде этого:

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

Лично я не думаю, что IOUtils.toString(InputStream)это "хорошо" - потому что он всегда использует кодировку платформы по умолчанию, что почти никогда не бывает тем, что вам нужно. Есть перегрузка, которая принимает имя кодировки, но использование имен - не лучшая идея, ИМО. Вот почему мне нравится Charsets.*.

РЕДАКТИРОВАТЬ: Не то, чтобы вышеупомянутое нуждается InputSupplier<InputStream>в streamSupplier. Если у вас уже есть поток, вы можете легко реализовать его:

InputSupplier<InputStream> supplier = new InputSupplier<InputStream>() {
    @Override public InputStream getInput() {
        return stream;
    }
};
Джон Скит
источник
Джон, идет поток через request.getInputStream? Кроме того, закроет ли ваш поток, как ColinD, упомянутый в ответе @Calum?
Blankman
О, и это среда сервлета doPost, мне все равно закрывать поток?
Blankman
@Blankman: Ах, вот и ваш контекст - из вашего вопроса это совсем не ясно. Неважно, закрываете ли вы поток запросов, но обычно я бы так и сделал. Я отредактирую этот ответ - похоже, такой перегрузки нет.
Джон Скит,
1
Я просто делаю это сейчас: String payLoad = CharStreams.toString (new InputStreamReader (request.getInputStream (), «UTF-8»));
Blankman
1
@BeeOnRope: Думаю, есть один промежуточный подход Charsets.UTF_8.name()- более устойчивый к опечаткам.
Джон Скит,
11

Другой вариант - прочитать байты из Stream и создать из них String:

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

Это не «чистая» гуава, но она немного короче.

пономандр
источник
К сожалению, ByteStreams.toByteArray()не закрывает поток, согласно Javadoc.
The Alchemist
Это правда. Я не видел никакой функции Guava, закрывающей поток. Ну разве что близко Тихо.
ponomandr
1
Как правило, поток открывается в операторе try-with-resources и закрывается автоматически, поэтому toByteArray () не должен входить в сферу ответственности
ponomandr
4

Основываясь на принятом ответе, вот служебный метод, который имитирует поведение IOUtils.toString()(а также перегруженную версию с кодировкой). Эта версия должна быть безопасной, правда?

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);
    }
}
Шон Патрик Флойд
источник
Мне кажется, это нормально. Материал ввода-вывода Guava работает лучше всего, если вы научитесь мыслить в терминах поставщиков многоразового ввода, а не однократных потоков и читателей (когда это возможно), но я думаю, поскольку вы конвертируете существующий код IOUtils, это будет большим изменением.
ColinD,
2
В моей гуаве 14 closeQuietly уже устарела. Предлагается использовать функцию try-with-resources, которая существует в Java 7. Подробнее на code.google.com/p/guava-libraries/wiki/…
bertie
2
@AlbertKam согласился. Но помните: этому ответу три года.
Шон Патрик Флойд
@SeanPatrickFloyd: Спасибо! На самом деле я перешел к более новому решению, начиная с вашего ответа. Я подумывал добавить комментарий для тех, кто может использовать более новую версию. :)
Берти 08
4

Существует гораздо более короткое решение для автоматического закрытия в случае, когда входной поток поступает из ресурса пути к классам:

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

Использует ресурсы Guava , вдохновленные IOExplained .

Вадим
источник
1
Когда был задан этот вопрос, класса Resources не существовало, но вы правы: сегодня это, вероятно, будет правильным решением. Спасибо
Шон Патрик Флойд
2

РЕДАКТИРОВАТЬ (2015): Okio - лучшая абстракция и инструменты для ввода-вывода на Java / Android, о которых я знаю. Я использую это все время.

FWIW вот что я использую.

Если у меня уже есть поток на руках, то:

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

Если я создаю поток:

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

В качестве конкретного примера я могу прочитать ресурс текстового файла Android следующим образом:

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
источник
Все устарели сейчас. :(
user3562927
1
Попробуйте вместо этого github.com/square/okio - я давно не использовал ввод-вывод Guava,
Okio
0

В качестве конкретного примера, вот как я могу прочитать ресурс текстового файла 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
источник