相当于IOUtils.toString(InputStream)的番石榴


106

Apache Commons IO有一个很好的便捷方法IOUtils.toString()来读取InputStream字符串。

由于我正尝试从Apache Commons转移到GuavaGuava中有等同的功能吗?我查看了com.google.common.io程序包中的所有类,但几乎找不到任何简单的东西。

编辑:我理解并赞赏字符集的问题。碰巧,我知道我所有的来源都是ASCII(是,ASCII,不是ANSI等),因此在这种情况下,编码对我来说不是问题。


2
关于字符集:库仍然要求您指定您知道要处理的字符集(例如Charsets.US_ASCII),而不是让您说“嗯,我猜是什么字符集?” 仍然很好。这对许多人来说似乎都很高兴。尤其是因为Java不使用有意义的默认值,例如UTF-8。
ColinD

我知道。这就是为什么我在自己的答案中使用UTF-8作为默认版本的原因。
肖恩·帕特里克·弗洛伊德


@Vadzim当问这个问题时,这些文档不存在:-)
肖恩·帕特里克·弗洛伊德

Answers:


85

您在对Calum答案的评论中表示要使用

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

该代码是有问题的,因为重载CharStreams.toString(Readable)指出:

不关闭Readable

这意味着在此代码完成后,您的InputStreamReader,以及作为扩展名的InputStreamby 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年2月

InputSupplier并且OutputSupplier在Guava 16.0中已弃用使用它们的方法。其接替者ByteSourceCharSourceByteSinkCharSink。给定一个ByteSource,您现在可以String像这样获取其内容:

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

感谢您的宝贵信息(+1)。但这非常冗长。我认为将接受的答案与Closeables.closeQuietly()结合起来会更容易。
肖恩·帕特里克·弗洛伊德

@CollinD:我在回答之一中使用了您的方法,请看一下代码并告诉我这是否是使用InputSupplier的正确方法。
艾米尔(Emil)2010年

1
@ColinD,如果inputStream来自doPost servlet内部,那么关闭它有什么意义吗?(或担心关闭它)
Blankman

CharStreams.toString(InputSupplier)现在已弃用。我创建了一个CharSource(使用asCharSource从ByteSource中获取),然后按照文档建议使用其toString。
约翰·莱曼

4
@ TedM.Young:如果您拥有的只是一个InputStream,而您想将其作为一个String,那CharStreams.toString(new InputStreamReader(inputStream, charset))就是要走的路。ByteSource并且CharSource是专门为在那里你有什么事情,可以作为源起案件InputStream秒或Reader秒。
ColinD 2014年

56

如果您有Readable可以使用CharStreams.toString(Readable)。因此,您可以执行以下操作:

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

强迫您指定一个字符集,我想您还是应该这样做。


4
实际上,我将结合您和Jon Skeet的回答:`CharStreams.toString(new InputStreamReader(supplier.get(),Charsets.UTF_8))`
肖恩·帕特里克·弗洛伊德

是的,有很多方法可以组合这些选项!
Calum 2010年

10
@SPFloyd:如果您有InputSupplier<InputStream>建议,我强烈建议使用CharStreams.newReaderSupplier(supplier, Charsets.UTF_8)而不是new InputStreamReader。原因是,给定时InputStreamReadertoString不会关闭它Reader(因此不会关闭基础流!)。通过将InputSupplierfor用作Reader,该toString方法将为您处理关闭Reader
ColinD 2010年

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)是“不错的”-因为它始终使用平台的默认编码,而这几乎永远不是您想要的。有一个重载使用编码的名称,但是使用名称并不是IMO的好主意。这就是为什么我喜欢Charsets.*

编辑:不是以上需要InputSupplier<InputStream>作为streamSupplier。如果您已经有了流,则可以通过以下方法轻松实现:

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

乔恩,是通过request.getInputStream流吗?另外,您会像@Calum的答案中提到的ColinD一样关闭流吗?
Blankman

哦,这是一个servlet doPost环境,无论如何我应该关闭流吗?
Blankman '04

@布兰克曼:啊,那是你的情况-你的问题根本不清楚。是否关闭请求流并不重要,但我通常会这样做。不过,我将编辑此答案-似乎没有这样的过载。
乔恩·斯基特

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的说法,它不会关闭流。
2015年

确实如此。我没有看到任何关闭流的Guava函数。好吧,除了closeQuietly。
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);
    }
}

在我看来很好。如果您学会考虑可重用的输入供应商,而不是使用1发流和读取器(如果可能),那么Guava的IO东西效果最好。
ColinD 2010年

2
在我的番石榴14中,closeQuietly已被弃用。该建议是使用try-与资源特点,在Java中7更多关于此存在code.google.com/p/guava-libraries/wiki/...
伯蒂

2
@AlbertKam表示同意。但是请记住:这个答案已经三岁了。
肖恩·帕特里克·弗洛伊德

@SeanPatrickFloyd:谢谢!实际上,我从您的答案开始就获得了较新的解决方案。我正在考虑为可能正在使用较新版本的其他人添加评论。:)
伯蒂

4

如果输入流来自类路径资源,则自动关闭解决方案要短得多:

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

使用受IOExplained启发的番石榴资源


1
提出此问题时,资源类并不存在,但是您是对的:今天,这可能是解决之道。谢谢
肖恩·帕特里克·弗洛伊德

2

编辑(2015):Okio是我所知道的Java / Android中最好的I / O抽象和工具。我用它所有的时间。

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

全部弃用。:(
user3562927

1
改为尝试github.com/square/okio-我已经有一段时间没有使用Guava的I / O了,
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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.