Guava equivalent for IOUtils.toString(InputStream)

Apache Commons IO has a nice convenience method IOUtils.toString() to read an InputStream to a String.

Since I am trying to move away from Apache Commons and to Guava: is there an equivalent in Guava? I looked at all classes in the com.google.common.io package and I couldn't find anything nearly as simple.

Edit: I understand and appreciate the issues with charsets. It just so happens that I know that all my sources are in ASCII (yes, ASCII, not ANSI etc.), so in this case, encoding is not an issue for me.

69903 次浏览

If you've got a Readable you can use CharStreams.toString(Readable). So you can probably do the following:

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

Forces you to specify a character set, which I guess you should be doing anyway.

Nearly. You could use something like this:

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

Personally I don't think that IOUtils.toString(InputStream) is "nice" - because it always uses the default encoding of the platform, which is almost never what you want. There's an overload which takes the name of the encoding, but using names isn't a great idea IMO. That's why I like Charsets.*.

EDIT: Not that the above needs an InputSupplier<InputStream> as the streamSupplier. If you've already got the stream you can implement that easily enough though:

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

You stated in your comment on Calum's answer that you were going to use

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

This code is problematic because the overload CharStreams.toString(Readable) states:

Does not close the Readable.

This means that your InputStreamReader, and by extension the InputStream returned by supplier.get(), will not be closed after this code completes.

If, on the other hand, you take advantage of the fact that you appear to already have an InputSupplier<InputStream> and used the overload CharStreams.toString(InputSupplier<R extends Readable & Closeable>), the toString method will handle both the creation and closing of the Reader for you.

This is exactly what Jon Skeet suggested, except that there isn't actually any overload of CharStreams.newReaderSupplier that takes an InputStream as input... you have to give it an 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);

The point of InputSupplier is to make your life easier by allowing Guava to handle the parts that require an ugly try-finally block to ensure that resources are closed properly.

Edit: Personally, I find the following (which is how I'd actually write it, was just breaking down the steps in the code above)

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

to be far less verbose than this:

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

Which is more or less what you'd have to write to handle this properly yourself.


Edit: Feb. 2014

InputSupplier and OutputSupplier and the methods that use them have been deprecated in Guava 16.0. Their replacements are ByteSource, CharSource, ByteSink and CharSink. Given a ByteSource, you can now get its contents as a String like this:

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

Based on the accepted answer, here is a utility method that mocks the behavior of IOUtils.toString() (and an overloaded version with a charset, as well). This version should be safe, right?

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

UPDATE: Looking back, I don't like my old solution. Besides it is 2013 now and there are better alternatives available now for Java7. So here is what I use now:

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

or if with InputSupplier

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

EDIT (2015): Okio is the best abstraction and tools for I/O in Java/Android that I know of. I use it all the time.

FWIW here's what I use.

If I already have a stream in hand, then:

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

If I'm creating a stream:

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

As a concrete example, I can read an Android text file asset like this:

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

Another option is to read bytes from Stream and create a String from them:

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

It's not 'pure' Guava, but it's a little bit shorter.

For a concrete example, here's how I can read an Android text file asset:

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

There is much shorter autoclosing solution in case when input stream comes from classpath resource:

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

Uses Guava Resources, inspired by IOExplained.