如何在Java中将InputStream读取/转换为String?


4062

如果有java.io.InputStream对象,应如何处理该对象并产生一个String


假设我有一个InputStream包含文本数据的文件,并且想将其转换为String,因此例如可以将其写入日志文件。

InputStream将转换为的最简单方法是String什么?

public String convertStreamToString(InputStream is) {
    // ???
}

36
在这个问题的答案只有当你想读取流中的内容的工作完全(直到它关闭)。由于并非总是如此(带有保持活动连接的http请求不会关闭),因此这些方法会调用block(不向您提供内容)。
f1sh

21
需要知道并指定流的字符编码,否则遇到字符编码错误,因为您将根据运行代码的机器/操作系统/平台或版本使用随机选择的编码。也就是说,千万不能使用依赖于平台的默认编码方法。
ChristofferHammarström,2010年

11
只是为了从9年前的评论中获得乐趣,这些天我使用Groovy的“ String s = new File(” SomeFile.txt“)。text”一次读取整个文件,而且效果很好。我很乐意将groovy用于我的非生产(脚本)代码,并且-老实说,强迫您以Java的方式处理编码和超长文件无论如何对于生产代码都是一个好主意,因此它可以正常工作, Groovy适用于Java不太擅长的快速脚本-只需使用正确的工具即可完成工作。
Bill K

只是简化: ByteArrayOutputStream outputBytes = new ByteArrayOutputStream(); for(byte[] b = new byte[512]; 0 < inputStream.read(b); outputBytes.write(b)); return new String(outputBytes.toByteArray(), StandardCharsets.UTF_8);
Felypp Oliveira

使用Java 11的@BillK,您可以使用String s = Files.readString​(Path.of("SomeFile.txt"));一种语言所能获得的最好的语言,它将永远不会像您所描述的那样支持这种魔术类型转换。
Holger

Answers:


2530

一个很好的方法是使用Apache commons IOUtilsapache复制InputStreamStringWriter...

StringWriter writer = new StringWriter();
IOUtils.copy(inputStream, writer, encoding);
String theString = writer.toString();

甚至

// NB: does not close inputStream, you'll have to use try-with-resources for that
String theString = IOUtils.toString(inputStream, encoding); 

另外,ByteArrayOutputStream如果您不想混合使用Streams和Writers ,则可以使用


75
对于android开发人员来说,似乎android并未附带来自Apache的IOUtils。因此,您可以考虑参考其他答案。
Chris.Zou 2014年

47
目前,这是一个令人难以置信的古老问题(2008年提出)。值得您花时间阅读更现代的答案。有些使用来自Java 8库的本机调用。
Shadoninja

36
这个答案已经严重过时了,应该可以将其标记为此类(遗憾的是,这不是atm)。
codepleb 2016年

7
IOUtils.toString()早已被弃用。绝对不再建议使用此答案。
Roshan

7
然后对其进行编辑,以说明为什么不推荐使用它来帮助将来的读者。
让·弗朗索瓦·法布尔

2485

总结其他答案,我发现了11种主要方法(请参见下文)。我编写了一些性能测试(请参见下面的结果):

将InputStream转换为字符串的方法:

  1. 使用IOUtils.toString(Apache Utils)

    String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
  2. 使用CharStreams(番石榴)

    String result = CharStreams.toString(new InputStreamReader(
          inputStream, Charsets.UTF_8));
  3. 使用Scanner(JDK)

    Scanner s = new Scanner(inputStream).useDelimiter("\\A");
    String result = s.hasNext() ? s.next() : "";
  4. 使用Stream API(Java 8)。警告:此解决方案会将不同的换行符(如\r\n)转换为\n

    String result = new BufferedReader(new InputStreamReader(inputStream))
      .lines().collect(Collectors.joining("\n"));
  5. 使用并行流API(Java 8)。警告:此解决方案会将不同的换行符(如\r\n)转换为\n

    String result = new BufferedReader(new InputStreamReader(inputStream)).lines()
       .parallel().collect(Collectors.joining("\n"));
  6. 使用InputStreamReaderStringBuilder(JDK)

    final int bufferSize = 1024;
    final char[] buffer = new char[bufferSize];
    final StringBuilder out = new StringBuilder();
    Reader in = new InputStreamReader(stream, StandardCharsets.UTF_8);
    int charsRead;
    while((charsRead = in.read(buffer, 0, buffer.length)) > 0) {
        out.append(buffer, 0, charsRead);
    }
    return out.toString();
  7. 使用StringWriterIOUtils.copy(Apache Commons)

    StringWriter writer = new StringWriter();
    IOUtils.copy(inputStream, writer, "UTF-8");
    return writer.toString();
  8. 使用ByteArrayOutputStreaminputStream.read(JDK)

    ByteArrayOutputStream result = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int length;
    while ((length = inputStream.read(buffer)) != -1) {
        result.write(buffer, 0, length);
    }
    // StandardCharsets.UTF_8.name() > JDK 7
    return result.toString("UTF-8");
  9. 使用BufferedReader(JDK)。警告:此解决方案将不同的换行符(如\n\r)转换为line.separator系统属性(例如,在Windows中转换为“ \ r \ n”)。

    String newLine = System.getProperty("line.separator");
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    StringBuilder result = new StringBuilder();
    boolean flag = false;
    for (String line; (line = reader.readLine()) != null; ) {
        result.append(flag? newLine: "").append(line);
        flag = true;
    }
    return result.toString();
  10. 使用BufferedInputStreamByteArrayOutputStream(JDK)

    BufferedInputStream bis = new BufferedInputStream(inputStream);
    ByteArrayOutputStream buf = new ByteArrayOutputStream();
    int result = bis.read();
    while(result != -1) {
        buf.write((byte) result);
        result = bis.read();
    }
    // StandardCharsets.UTF_8.name() > JDK 7
    return buf.toString("UTF-8");
  11. 使用inputStream.read()StringBuilder(JDK)。警告:此解决方案存在Unicode问题,例如俄语文本(仅适用于非Unicode文本)

    int ch;
    StringBuilder sb = new StringBuilder();
    while((ch = inputStream.read()) != -1)
        sb.append((char)ch);
    reset();
    return sb.toString();

警告

  1. 解决方案4、5和9将不同的换行符转换为1。

  2. 解决方案11不能正确处理Unicode文本

性能测试

针对小型String(长度= 175),github中 url的性能测试(模式=平均时间,系统= Linux,满分1,343分是最好的):

              Benchmark                         Mode  Cnt   Score   Error  Units
 8. ByteArrayOutputStream and read (JDK)        avgt   10   1,343 ± 0,028  us/op
 6. InputStreamReader and StringBuilder (JDK)   avgt   10   6,980 ± 0,404  us/op
10. BufferedInputStream, ByteArrayOutputStream  avgt   10   7,437 ± 0,735  us/op
11. InputStream.read() and StringBuilder (JDK)  avgt   10   8,977 ± 0,328  us/op
 7. StringWriter and IOUtils.copy (Apache)      avgt   10  10,613 ± 0,599  us/op
 1. IOUtils.toString (Apache Utils)             avgt   10  10,605 ± 0,527  us/op
 3. Scanner (JDK)                               avgt   10  12,083 ± 0,293  us/op
 2. CharStreams (guava)                         avgt   10  12,999 ± 0,514  us/op
 4. Stream Api (Java 8)                         avgt   10  15,811 ± 0,605  us/op
 9. BufferedReader (JDK)                        avgt   10  16,038 ± 0,711  us/op
 5. parallel Stream Api (Java 8)                avgt   10  21,544 ± 0,583  us/op

针对大型String(长度= 50100),github中的 url的性能测试(模式=平均时间,系统= Linux,得分200,715是最好的):

               Benchmark                        Mode  Cnt   Score        Error  Units
 8. ByteArrayOutputStream and read (JDK)        avgt   10   200,715 ±   18,103  us/op
 1. IOUtils.toString (Apache Utils)             avgt   10   300,019 ±    8,751  us/op
 6. InputStreamReader and StringBuilder (JDK)   avgt   10   347,616 ±  130,348  us/op
 7. StringWriter and IOUtils.copy (Apache)      avgt   10   352,791 ±  105,337  us/op
 2. CharStreams (guava)                         avgt   10   420,137 ±   59,877  us/op
 9. BufferedReader (JDK)                        avgt   10   632,028 ±   17,002  us/op
 5. parallel Stream Api (Java 8)                avgt   10   662,999 ±   46,199  us/op
 4. Stream Api (Java 8)                         avgt   10   701,269 ±   82,296  us/op
10. BufferedInputStream, ByteArrayOutputStream  avgt   10   740,837 ±    5,613  us/op
 3. Scanner (JDK)                               avgt   10   751,417 ±   62,026  us/op
11. InputStream.read() and StringBuilder (JDK)  avgt   10  2919,350 ± 1101,942  us/op

图形(性能测试取决于Windows 7系统中的输入流长度)
在此处输入图片说明

性能测试(平均时间)取决于Windows 7系统中的输入流长度:

 length  182    546     1092    3276    9828    29484   58968

 test8  0.38    0.938   1.868   4.448   13.412  36.459  72.708
 test4  2.362   3.609   5.573   12.769  40.74   81.415  159.864
 test5  3.881   5.075   6.904   14.123  50.258  129.937 166.162
 test9  2.237   3.493   5.422   11.977  45.98   89.336  177.39
 test6  1.261   2.12    4.38    10.698  31.821  86.106  186.636
 test7  1.601   2.391   3.646   8.367   38.196  110.221 211.016
 test1  1.529   2.381   3.527   8.411   40.551  105.16  212.573
 test3  3.035   3.934   8.606   20.858  61.571  118.744 235.428
 test2  3.136   6.238   10.508  33.48   43.532  118.044 239.481
 test10 1.593   4.736   7.527   20.557  59.856  162.907 323.147
 test11 3.913   11.506  23.26   68.644  207.591 600.444 1211.545

17
在编写“摘要答案”时,应注意,某些解决方案会自动将不同的换行符(如\r\n)转换\n为某些情况下不希望的换行符。同样很高兴看到所需的额外内存或至少分配压力(至少您可以使用运行JMH -prof gc)。对于非常酷的帖子,很高兴看到图表(取决于相同输入大小内的字符串长度和相同字符串内的输入大小)。
塔吉尔·瓦列夫

16
已投票;最有趣的是,结果超出了预期:应该使用标准的JDK和/或Apache Commons语法糖。
Aleksei Matiushkin '16

25
很棒的帖子。就一件事。Java 8警告不要在资源上使用并行流,这将迫使您锁定并等待(例如此输入流),因此并行流选项相当繁琐,不值得吗?
mangusbrother

10
并行流实际上是否维持行顺序?
Natix

6
什么是reset()例如11?
罗伯·斯图尔特

2307

这是仅使用标准Java库的方法(请注意,流未关闭,您的行程可能会有所不同)。

static String convertStreamToString(java.io.InputStream is) {
    java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
    return s.hasNext() ? s.next() : "";
}

我从“愚蠢的扫描程序技巧”一文中学到了这一技巧。它起作用的原因是因为Scanner遍历流中的令牌,在这种情况下,我们使用“输入边界的开头”(\ A)来分隔令牌,因此对于流的整个内容仅给我们一个令牌。

请注意,如果您需要具体说明输入流的编码,则可以提供第二个参数 Scanner构造函数指示要使用的字符集(例如“ UTF-8”)。

雅各布(Jacob)对此表示感谢,他曾经将我指向上述文章。


8
谢谢,对于我的版本,我添加了一个finally块来关闭输入流,因此自从您完成输入的读取之后,用户就不必这样做了。大大简化了调用者代码。

4
@PavelRepin @Patrick在我的情况下,在Scanner构建期间,一个空的inputStream导致了NPE。我必须if (is == null) return "";在方法开始时添加一些内容。我认为此答案需要更新以更好地处理null inputStreams。
CFL_Jeff 2012年

115
对于Java 7,您可以尝试以下方法关闭: try(java.util.Scanner s = new java.util.Scanner(is)) { return s.useDelimiter("\\A").hasNext() ? s.next() : ""; }
earcam 2013年

5
不幸的是,此解决方案似乎消失了,并且丢失了在我的基础流实现中引发的异常。
泰格

11
仅供参考,控制台输入流上的hasNext块(请参阅此处)。(现在就遇到了这个问题。)否则,此解决方案可以很好地工作……只是要提防。
瑞安

848

Apache Commons允许:

String myString = IOUtils.toString(myInputStream, "UTF-8");

当然,您可以选择UTF-8以外的其他字符编码。

另请参阅:(文档


1
另外,如果找到具有默认编码的方法,则有一种方法仅采用inputStream参数。
GuillaumeCoté2011年

13
@GuillaumeCoté我想这里的信息是,您永远都不应“使用默认编码进行优化”,因为您不能确定它是什么,具体取决于运行Java代码的平台。
Per Wiklander 2011年

7
@Per Wiklander我不同意你的看法。可以在单个代码上运行的代码可以肯定,默认编码会很好。对于仅打开本地文件的代码,要求它们以平台默认编码进行编码是一种合理的选择。
GuillaumeCoté2011年

39
为了节省任何人使用Google搜索的麻烦-<dependency> <groupId> org.apache.commons </ groupId> <artifactId> commons-io </ artifactId> <version> 1.3.2 </ version> </ dependency>
克里斯·

7
同样,使用apache io(或其他)常量进行字符编码也不会使用简单的字符串原义-例如:IOUtils.toString(myInputStream,Charsets.UTF_8);

300

考虑到文件,应该首先获得一个java.io.Reader实例。然后可以将其读取并添加到中StringBuilderStringBuffer如果我们不在多个线程中访问它,则不需要,而且StringBuilder速度更快)。这里的窍门是我们在块中工作,因此不需要其他缓冲流。参数化块大小以进行运行时性能优化。

public static String slurp(final InputStream is, final int bufferSize) {
    final char[] buffer = new char[bufferSize];
    final StringBuilder out = new StringBuilder();
    try (Reader in = new InputStreamReader(is, "UTF-8")) {
        for (;;) {
            int rsz = in.read(buffer, 0, buffer.length);
            if (rsz < 0)
                break;
            out.append(buffer, 0, rsz);
        }
    }
    catch (UnsupportedEncodingException ex) {
        /* ... */
    }
    catch (IOException ex) {
        /* ... */
    }
    return out.toString();
}

8
此解决方案使用多字节字符。该示例使用UTF-8编码,该编码允许表达完整的unicode范围(包括中文)。用其他编码替换“ UTF-8”将允许使用该编码。
Paul de Vrieze 2011年

27
@ User1-我喜欢在代码中使用库,因此可以更快地完成工作。当您的经理说“哇,詹姆斯!您是怎么这么快完成的?!”时,这真棒。但是,当我们仅仅因为对包含一个通用的,可重用的,经过实践检验的实用程序的想法放错了位置而不得不花时间重新发明轮子时,我们正在浪费时间,我们可能会花费更多的精力来实现我们的项目目标。当我们重新发明轮子时,我们的工作量增加了一倍,但到了很晚才到达终点。一旦我们到达终点,就没有人祝贺我们了。
盖房子时

10
抱歉,重新阅读我的评论后,它有点自大了。我只是认为有充分的理由避免图书馆很重要,而且这个理由是有效的,这很可能是:)
jmort253

4
@ jmort253在多次更新产品中的某些库后,我们注意到性能下降。幸运的是,我们正在构建和销售自己的产品,因此我们实际上没有所谓的截止日期。不幸的是,我们正在构建一个可以在许多操作系统上的许多JVM,数据库和应用程序服务器上使用的产品,因此我们必须考虑使用较差计算机的用户……而字符串操作优化可以将性能提高30%至40%。并修复:In our product, I even replaced应该是“我们甚至替换了”。
coolcfan 2012年

10
@ jmort253如果我已经说过要使用apache commons,那就去吧。同时,使用库存在实际成本(正如许多apache java库中的依赖关系激增所示)。如果这是该库的唯一用途,那么使用该库就太过分了。另一方面,确定自己的缓冲区大小后,可以调整内存/处理器使用平衡。
保罗·德弗里兹

248

采用:

InputStream in = /* Your InputStream */;
StringBuilder sb = new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String read;

while ((read=br.readLine()) != null) {
    //System.out.println(read);
    sb.append(read);
}

br.close();
return sb.toString();

11
关键是,您首先要拆分成几行,然后再将其撤消。读取任意缓冲区更容易,更快捷。
Paul de Vrieze

20
另外,readLine不能区分\ n和\ r,因此您无法再次再现确切的流。
玛丽亚·阿里亚斯·雷纳多明戈斯

2
效率很低,因为readLine逐字符读取以查找EOL。另外,如果流中没有换行符,那么这实际上没有意义。
njzk2 2014年

3
@Gops AB:如果您尝试这样做,并且示例中包含换行符,您将看到使用readline()和StringBuilder.append()构造此循环的方式实际上并未保留换行符。
Russ Bateman

4
这不是最好的答案,因为它不是严格的逐字节输出。读者会选择换行符,因此您必须小心维护它们。
Jeffrey Blattman '16

173

如果您使用的是Google馆藏/番石榴,则可以执行以下操作:

InputStream stream = ...
String content = CharStreams.toString(new InputStreamReader(stream, Charsets.UTF_8));
Closeables.closeQuietly(stream);

请注意,InputStreamReader不需要第二个参数(即Charsets.UTF_8),但是通常最好指定编码(如果您知道的话)(应该这样做)。


2
@harschware:给出的问题是:“如果有java.io.InputStream对象,应该如何处理该对象并产生String?” 我假设情况中已经存在流。
Sakuraba

您没有很好地解释您的答案,并且有多余的变量。user359996说的话与您相同,但更清楚。
Uronym 2011年

2
+1表示番石榴,-1表示未指定输入流的编码。例如。新的InputStreamReader(stream,“ UTF-8”)
andras 2012年

@Chris Noldus另一方面,像我一样,有些人已经在他们的项目中使用了番石榴,并且认为此解决方案比仅sdk版本更优雅。
CorayThan

@Vadzim的答案与此答案相同-都使用CharStreams.toString
Tom

125

这是最好的纯Java解决方案,非常适合Android和任何其他JVM。

该解决方案效果非常好……它简单,快速,并且在大小溪流上都一样!!(请参阅上面的基准。第8号

public String readFullyAsString(InputStream inputStream, String encoding)
        throws IOException {
    return readFully(inputStream).toString(encoding);
}

public byte[] readFullyAsBytes(InputStream inputStream)
        throws IOException {
    return readFully(inputStream).toByteArray();
}

private ByteArrayOutputStream readFully(InputStream inputStream)
        throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int length = 0;
    while ((length = inputStream.read(buffer)) != -1) {
        baos.write(buffer, 0, length);
    }
    return baos;
}

4
与其他答案(仅适用于企业Java)相比,在Android上效果很好。
vortexwolf

在Android中,每次输入短字符串时,在“ .write”行上均出现OutOfMemory错误而崩溃。
亚当

我添加了编码。顺便提一句,我在代码中拥有的原始readFully方法不会返回String,它会返回byte []以实现更通用的功能。使用API​​实现新的String(...)编码是其责任!
TacB0sS

2
快速说明:根据自动增长的系统2*n,其内存占用量最大为,其中n是流的大小ByteArrayInputStream
njzk2 2014年

3
不必要地使内存使用量增加一倍,这在移动设备上非常宝贵。您最好使用InputStreamReader并将其追加到StringReader,字节到char的转换将即时进行,而不是在最后大量进行。
奥利夫,2015年

83

为了完整起见,这里是Java 9解决方案:

public static String toString(InputStream input) throws IOException {
    return new String(input.readAllBytes(), StandardCharsets.UTF_8);
}

readAllBytes目前在JDK 9主要的基本代码,因此它可能出现在释放。您现在可以使用JDK 9快照构建进行尝试。


该方法是否不分配大量内存用于读取?byte[] buf = new byte[DEFAULT_BUFFER_SIZE];哪里MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;MAX_BUFFER_SIZE = 2147483639。谷歌表示其约为2.147 GB。
Rekin 2015年

抱歉,我在计算中出错。这是2 GB。我已经编辑了评论。所以,即使我读了4kb的文件,我也会使用2gb的内存?
Rekin 2015年

2
@ChristianHujer,我在最新的jdk8u commit中没有看到它。Java更新从未引入AFAIK新方法,只有主要版本才引入。
塔吉尔·瓦列夫

4
@ChristianHujer,问题是关于InputStream而不是Path。该InputStream可以从许多不同的来源产生,不仅文件。
塔吉尔·瓦列夫

5
这是一年前编写的,因此要进行更新,我确认此方法确实在公共发行版JDK 9中。此外,如果您的编码是“ ISO-Latin-1”,那么这将非常有效,因为现在使用Java 9 Strings byte[]如果所有字符都在前256个代码点中,则为一个实现。这意味着新的String(byte [],“ ISO-Latin-1”)将是一个简单的数组副本。
Klitos Kyriacou

66

采用:

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;

public static String readInputStreamAsString(InputStream in)
    throws IOException {

    BufferedInputStream bis = new BufferedInputStream(in);
    ByteArrayOutputStream buf = new ByteArrayOutputStream();
    int result = bis.read();
    while(result != -1) {
      byte b = (byte)result;
      buf.write(b);
      result = bis.read();
    }
    return buf.toString();
}

@DanielDeLeón不,不是。这是一个BufferedInputStream。基本读取一次为8192字节。
user207421 '17

2
@EJP我发现它比使用BufferedInputStream 读取字节数组缓冲区而不是一次读取一个字节要慢。示例:读取4.56 MiB文件时200ms与60ms。
jk7

奇怪的是,没有人指出这里的另一个主要问题(是的,即使使用缓冲,逐字节读取内容也是浪费的):它依赖于碰巧是“默认编码”的东西-这很少是一种好方法。相反,请确保将encoding作为参数传递给buf.toString()
StaxMan

@ jk7读取4.56MB文件的时间非常短,以至于差异不可能很大。
user207421 '19

63

经过一些实验,这是我想到的最优雅的纯Java(无库)解决方案:

public static String fromStream(InputStream in) throws IOException
{
    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
    StringBuilder out = new StringBuilder();
    String newLine = System.getProperty("line.separator");
    String line;
    while ((line = reader.readLine()) != null) {
        out.append(line);
        out.append(newLine);
    }
    return out.toString();
}

8
@TorbenKohlmeier,读取器和缓冲区不需要关闭。提供者InputStream应由调用者关闭。
Drew Noakes 2013年

7
别忘了提到InputStreamReader中有一个更可取的构造函数,它采用CharSet。
jontejj 2013年

7
人们为什么继续使用readLine?如果您本身不使用线路,那有什么好处(除了非常慢之外?)
njzk2 2014年

4
不要按行阅读。如果一行太长以致不能放入堆怎么办?
voho 2014年

4
@voho,如果一行那么长,那么就无法分配返回值,该值必须等于或大于该行的大小。如果要处理的文件太大,则应流式传输它们。不过,有很多用例可以将小的文本文件加载到内存中。
Drew Noakes 2014年

55

我对这里的14个不同答案做了一个基准测试(很抱歉,您没有提供积分,但是重复太多了)。

结果非常令人惊讶。事实证明,Apache IOUtils是最慢的,ByteArrayOutputStream也是最快的解决方案:

所以首先这里是最好的方法:

public String inputStreamToString(InputStream inputStream) throws IOException {
    try(ByteArrayOutputStream result = new ByteArrayOutputStream()) {
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) != -1) {
            result.write(buffer, 0, length);
        }

        return result.toString(UTF_8);
    }
}

基准测试结果,在20个周期内产生20 MB随机字节

时间(以毫秒为单位)

  • ByteArrayOutputStreamTest:194
  • NioStream:198
  • Java9ISTransferTo:201
  • Java9ISReadAllBytes:205
  • BufferedInputStreamVsByteArrayOutputStream:314
  • ApacheStringWriter2:574
  • GuavaChar流:589
  • ScannerReaderNoNextTest:614
  • 扫描仪阅读器:633
  • ApacheStringWriter:1544年
  • StreamApi:错误
  • ParallelStreamApi:错误
  • BufferReaderTest:错误
  • InputStreamAndStringBuilder:错误

基准源代码

import com.google.common.io.CharStreams;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

/**
 * Created by Ilya Gazman on 2/13/18.
 */
public class InputStreamToString {


    private static final String UTF_8 = "UTF-8";

    public static void main(String... args) {
        log("App started");
        byte[] bytes = new byte[1024 * 1024];
        new Random().nextBytes(bytes);
        log("Stream is ready\n");

        try {
            test(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void test(byte[] bytes) throws IOException {
        List<Stringify> tests = Arrays.asList(
                new ApacheStringWriter(),
                new ApacheStringWriter2(),
                new NioStream(),
                new ScannerReader(),
                new ScannerReaderNoNextTest(),
                new GuavaCharStreams(),
                new StreamApi(),
                new ParallelStreamApi(),
                new ByteArrayOutputStreamTest(),
                new BufferReaderTest(),
                new BufferedInputStreamVsByteArrayOutputStream(),
                new InputStreamAndStringBuilder(),
                new Java9ISTransferTo(),
                new Java9ISReadAllBytes()
        );

        String solution = new String(bytes, "UTF-8");

        for (Stringify test : tests) {
            try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
                String s = test.inputStreamToString(inputStream);
                if (!s.equals(solution)) {
                    log(test.name() + ": Error");
                    continue;
                }
            }
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 20; i++) {
                try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
                    test.inputStreamToString(inputStream);
                }
            }
            log(test.name() + ": " + (System.currentTimeMillis() - startTime));
        }
    }

    private static void log(String message) {
        System.out.println(message);
    }

    interface Stringify {
        String inputStreamToString(InputStream inputStream) throws IOException;

        default String name() {
            return this.getClass().getSimpleName();
        }
    }

    static class ApacheStringWriter implements Stringify {

        @Override
        public String inputStreamToString(InputStream inputStream) throws IOException {
            StringWriter writer = new StringWriter();
            IOUtils.copy(inputStream, writer, UTF_8);
            return writer.toString();
        }
    }

    static class ApacheStringWriter2 implements Stringify {

        @Override
        public String inputStreamToString(InputStream inputStream) throws IOException {
            return IOUtils.toString(inputStream, UTF_8);
        }
    }

    static class NioStream implements Stringify {

        @Override
        public String inputStreamToString(InputStream in) throws IOException {
            ReadableByteChannel channel = Channels.newChannel(in);
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 16);
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            WritableByteChannel outChannel = Channels.newChannel(bout);
            while (channel.read(byteBuffer) > 0 || byteBuffer.position() > 0) {
                byteBuffer.flip();  //make buffer ready for write
                outChannel.write(byteBuffer);
                byteBuffer.compact(); //make buffer ready for reading
            }
            channel.close();
            outChannel.close();
            return bout.toString(UTF_8);
        }
    }

    static class ScannerReader implements Stringify {

        @Override
        public String inputStreamToString(InputStream is) throws IOException {
            java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
            return s.hasNext() ? s.next() : "";
        }
    }

    static class ScannerReaderNoNextTest implements Stringify {

        @Override
        public String inputStreamToString(InputStream is) throws IOException {
            java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
            return s.next();
        }
    }

    static class GuavaCharStreams implements Stringify {

        @Override
        public String inputStreamToString(InputStream is) throws IOException {
            return CharStreams.toString(new InputStreamReader(
                    is, UTF_8));
        }
    }

    static class StreamApi implements Stringify {

        @Override
        public String inputStreamToString(InputStream inputStream) throws IOException {
            return new BufferedReader(new InputStreamReader(inputStream))
                    .lines().collect(Collectors.joining("\n"));
        }
    }

    static class ParallelStreamApi implements Stringify {

        @Override
        public String inputStreamToString(InputStream inputStream) throws IOException {
            return new BufferedReader(new InputStreamReader(inputStream)).lines()
                    .parallel().collect(Collectors.joining("\n"));
        }
    }

    static class ByteArrayOutputStreamTest implements Stringify {

        @Override
        public String inputStreamToString(InputStream inputStream) throws IOException {
            try(ByteArrayOutputStream result = new ByteArrayOutputStream()) {
                byte[] buffer = new byte[1024];
                int length;
                while ((length = inputStream.read(buffer)) != -1) {
                    result.write(buffer, 0, length);
                }

                return result.toString(UTF_8);
            }
        }
    }

    static class BufferReaderTest implements Stringify {

        @Override
        public String inputStreamToString(InputStream inputStream) throws IOException {
            String newLine = System.getProperty("line.separator");
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder result = new StringBuilder(UTF_8);
            String line;
            boolean flag = false;
            while ((line = reader.readLine()) != null) {
                result.append(flag ? newLine : "").append(line);
                flag = true;
            }
            return result.toString();
        }
    }

    static class BufferedInputStreamVsByteArrayOutputStream implements Stringify {

        @Override
        public String inputStreamToString(InputStream inputStream) throws IOException {
            BufferedInputStream bis = new BufferedInputStream(inputStream);
            ByteArrayOutputStream buf = new ByteArrayOutputStream();
            int result = bis.read();
            while (result != -1) {
                buf.write((byte) result);
                result = bis.read();
            }

            return buf.toString(UTF_8);
        }
    }

    static class InputStreamAndStringBuilder implements Stringify {

        @Override
        public String inputStreamToString(InputStream inputStream) throws IOException {
            int ch;
            StringBuilder sb = new StringBuilder(UTF_8);
            while ((ch = inputStream.read()) != -1)
                sb.append((char) ch);
            return sb.toString();
        }
    }

    static class Java9ISTransferTo implements Stringify {

        @Override
        public String inputStreamToString(InputStream inputStream) throws IOException {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            inputStream.transferTo(bos);
            return bos.toString(UTF_8);
        }
    }

    static class Java9ISReadAllBytes implements Stringify {

        @Override
        public String inputStreamToString(InputStream inputStream) throws IOException {
            return new String(inputStream.readAllBytes(), UTF_8);
        }
    }

}

在Java中进行基准测试并非易事(尤其是由于JIT)。在阅读了基准测试源代码之后,我确信上面的那些值并不精确,每个人都应谨慎对待它们。
DALIBOR

@Dalibor您可能应该为您的主张提供更多的理由,而不仅仅是链接。
伊利亚·加兹曼

我认为,众所周知,建立自己的基准并不容易。对于那些不知道的人,这里有链接;)
达利博尔

@Dalibor我可能不是最好的,但是我对Java基准测试有很好的了解,因此,除非您指出一个特定的问题,否则您将产生误导,在这种情况下,我不会继续与您​​进行对话。
伊利亚·加兹曼

我大多同意达利博。您说您“对Java基准测试有很好的理解”,但是您似乎实现了最幼稚的方法,同时显然不了解该方法的众所周知的问题。对于初学者,请阅读有关此问题的每篇文章:stackoverflow.com/questions/504103/…–
DavidS,

41

我会使用一些Java 8技巧。

public static String streamToString(final InputStream inputStream) throws Exception {
    // buffering optional
    try
    (
        final BufferedReader br
           = new BufferedReader(new InputStreamReader(inputStream))
    ) {
        // parallel optional
        return br.lines().parallel().collect(Collectors.joining("\n"));
    } catch (final IOException e) {
        throw new RuntimeException(e);
        // whatever.
    }
}

基本上与其他一些答案相同,只是更为简洁。


5
return null会被打电话吗?无论是br.lines...收益还是抛出一个异常。
Holloway 2014年

3
@Khaled A Khunaifer:是的,非常确定……也许您应该在这里看看:docs.oracle.com/javase/tutorial/essential/exceptions/…。您错误地编辑的是“尝试资源”语句。
2015年

11
您为什么要parallel()在视频流上通话?
robinst 2015年

4
这会不会导致一个诚实的数据的副本,如果源流使用Windows行结尾为所有\r\n最终会得到转换成\n...
卢卡斯

2
您可以使用System.lineSeparator()依赖于平台的适当行尾。
史蒂夫·K

34

我进行一些计时测试,因为时间总是很重要。

我试图以三种不同的方式将响应转换为字符串。(如下所示)
为了便于阅读,我省略了try / catch块。

为了提供上下文,这是所有3种方法的前面的代码:

   String response;
   String url = "www.blah.com/path?key=value";
   GetMethod method = new GetMethod(url);
   int status = client.executeMethod(method);

1)

 response = method.getResponseBodyAsString();

2)

InputStream resp = method.getResponseBodyAsStream();
InputStreamReader is=new InputStreamReader(resp);
BufferedReader br=new BufferedReader(is);
String read = null;
StringBuffer sb = new StringBuffer();
while((read = br.readLine()) != null) {
    sb.append(read);
}
response = sb.toString();

3)

InputStream iStream  = method.getResponseBodyAsStream();
StringWriter writer = new StringWriter();
IOUtils.copy(iStream, writer, "UTF-8");
response = writer.toString();

因此,在使用相同的请求/响应数据的每种方法上运行500个测试之后,这里是数字。再一次,这些是我的发现,您的发现可能并不完全相同,但是我写这篇文章的目的是向其他人表明这些方法的效率差异。

排名:
方法#1
方法#3-比#1慢2.6%
方法#2-比#1慢4.3%

这些方法中的任何一种都是用于获取响应并从中创建字符串的合适解决方案。


2
2)包含一个错误,它总是在字符串的末尾添加“ null”,因为您总是在需要时再执行一步。无论如何,我的表现都会是一样的。这应该起作用:String read = null; StringBuffer sb = new StringBuffer(); while((read = br.readLine())!= null){sb.append(read); }
LukeSolar 2011年

应当注意,GetMethod是org.apache.commons.httpclient的一部分,而不是标准的Java
jk7

如果文件有很多行,方法#2将消耗'\ n',但这不应该是答案
Ninja

33

从Java 8开始,使用Stream的纯Java解决方案就可以使用。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.stream.Collectors;

// ...
public static String inputStreamToString(InputStream is) throws IOException {
    try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
        return br.lines().collect(Collectors.joining(System.lineSeparator()));
    }
}

正如ChristofferHammarström在其他答案下面提到的那样,明确指定Charset更为安全。即,InputStreamReader构造函数可以进行如下更改:

new InputStreamReader(is, Charset.forName("UTF-8"))

11
而不是Charset.forName("UTF-8")使用StandardCharsets.UTF_8(from java.nio.charset)。
robinst 2015年

26

这或多或少是sampath的答案,它做了一些整理并以函数表示:

String streamToString(InputStream in) throws IOException {
  StringBuilder out = new StringBuilder();
  BufferedReader br = new BufferedReader(new InputStreamReader(in));
  for(String line = br.readLine(); line != null; line = br.readLine()) 
    out.append(line);
  br.close();
  return out.toString();
}


21

如果您不能使用Commons IO(FileUtils / IOUtils / CopyUtils),下面是使用BufferedReader逐行读取文件的示例:

public class StringFromFile {
    public static void main(String[] args) /*throws UnsupportedEncodingException*/ {
        InputStream is = StringFromFile.class.getResourceAsStream("file.txt");
        BufferedReader br = new BufferedReader(new InputStreamReader(is/*, "UTF-8"*/));
        final int CHARS_PER_PAGE = 5000; //counting spaces
        StringBuilder builder = new StringBuilder(CHARS_PER_PAGE);
        try {
            for(String line=br.readLine(); line!=null; line=br.readLine()) {
                builder.append(line);
                builder.append('\n');
            }
        } 
        catch (IOException ignore) { }

        String text = builder.toString();
        System.out.println(text);
    }
}

或者,如果您想要原始速度,我会建议Paul de Vrieze建议的变化形式(避免使用StringWriter(内部使用StringBuffer)):

public class StringFromFileFast {
    public static void main(String[] args) /*throws UnsupportedEncodingException*/ {
        InputStream is = StringFromFileFast.class.getResourceAsStream("file.txt");
        InputStreamReader input = new InputStreamReader(is/*, "UTF-8"*/);
        final int CHARS_PER_PAGE = 5000; //counting spaces
        final char[] buffer = new char[CHARS_PER_PAGE];
        StringBuilder output = new StringBuilder(CHARS_PER_PAGE);
        try {
            for(int read = input.read(buffer, 0, buffer.length);
                    read != -1;
                    read = input.read(buffer, 0, buffer.length)) {
                output.append(buffer, 0, read);
            }
        } catch (IOException ignore) { }

        String text = output.toString();
        System.out.println(text);
    }
}

为了使您的代码正常工作,我不得不使用this.getClass()。getClassLoader()。getResourceAsStream()(将Eclipse与Maven项目结合使用)
greuze 2012年

19

这个很好是因为:

  • 它可以安全地处理Charset。
  • 您可以控制读取缓冲区的大小。
  • 您可以设置构建器的长度,而不必是一个确切的值。
  • 没有库依赖。
  • 适用于Java 7或更高版本。

怎么做?

public static String convertStreamToString(InputStream is) throws IOException {
   StringBuilder sb = new StringBuilder(2048); // Define a size if you have an idea of it.
   char[] read = new char[128]; // Your buffer size.
   try (InputStreamReader ir = new InputStreamReader(is, StandardCharsets.UTF_8)) {
     for (int i; -1 != (i = ir.read(read)); sb.append(read, 0, i));
   }
   return sb.toString();
}

对于JDK 9

public static String inputStreamString(InputStream inputStream) throws IOException {
    try (inputStream) {
        return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
    }
}

1
catch (Throwable)真的不应该是空的,如果这是生产代码。
Christian Hujer '16

1
在该catch-throwable语句中应放置什么?
alex

虽然通常使用UTF-8是合理的,但是您不应该假设字符是通过这种方式编码的。
马丁

18

这是根据org.apache.commons.io.IOUtils 源代码改编而成的答案,适用于那些希望使用apache实现但又不需要整个库的人。

private static final int BUFFER_SIZE = 4 * 1024;

public static String inputStreamToString(InputStream inputStream, String charsetName)
        throws IOException {
    StringBuilder builder = new StringBuilder();
    InputStreamReader reader = new InputStreamReader(inputStream, charsetName);
    char[] buffer = new char[BUFFER_SIZE];
    int length;
    while ((length = reader.read(buffer)) != -1) {
        builder.append(buffer, 0, length);
    }
    return builder.toString();
}

18

如果使用流阅读器,请确保最后关闭流

private String readStream(InputStream iStream) throws IOException {
    //build a Stream Reader, it can read char by char
    InputStreamReader iStreamReader = new InputStreamReader(iStream);
    //build a buffered Reader, so that i can read whole line at once
    BufferedReader bReader = new BufferedReader(iStreamReader);
    String line = null;
    StringBuilder builder = new StringBuilder();
    while((line = bReader.readLine()) != null) {  //Read till end
        builder.append(line);
        builder.append("\n"); // append new line to preserve lines
    }
    bReader.close();         //close all opened stuff
    iStreamReader.close();
    //iStream.close(); //EDIT: Let the creator of the stream close it!
                       // some readers may auto close the inner stream
    return builder.toString();
}

编辑:在JDK 7+上,您可以使用try-with-resources构造。

/**
 * Reads the stream into a string
 * @param iStream the input stream
 * @return the string read from the stream
 * @throws IOException when an IO error occurs
 */
private String readStream(InputStream iStream) throws IOException {

    //Buffered reader allows us to read line by line
    try (BufferedReader bReader =
                 new BufferedReader(new InputStreamReader(iStream))){
        StringBuilder builder = new StringBuilder();
        String line;
        while((line = bReader.readLine()) != null) {  //Read till end
            builder.append(line);
            builder.append("\n"); // append new line to preserve lines
        }
        return builder.toString();
    }
}

2
关闭流是正确的,但是,关闭流的责任通常是由流构造函数负责(完成您的工作)。因此,iStream实际上应该由调用者关闭,因为调用者创建了iStream。此外,关闭流应该在一个finally块中完成,甚至最好在Java 7 try-with-resources语句中完成。在您的代码中,当readLine()throws IOExceptionbuilder.append()throws时OutOfMemoryError,流将保持打开状态。
Christian Hujer '16

16

对于所有Spring用户,还有一个:

import java.nio.charset.StandardCharsets;
import org.springframework.util.FileCopyUtils;

public String convertStreamToString(InputStream is) throws IOException { 
    return new String(FileCopyUtils.copyToByteArray(is), StandardCharsets.UTF_8);
}

中的实用程序方法与org.springframework.util.StreamUtils中的相似FileCopyUtils,但是完成后它们会保持流打开。


16

使用Java 9中支持的java.io.InputStream.transferTo(OutputStream)和使用字符集名称的ByteArrayOutputStream.toString(String)

public static String gobble(InputStream in, String charsetName) throws IOException {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    in.transferTo(bos);
    return bos.toString(charsetName);
}

在您的情况下,您为字符集名称传递了什么?
virsha

1
@virsha您必须从提供InputStream的源中确定这一点。请记住,不知道字符串使用什么编码就没有意义。
jmehrens

15

这是无需使用任何第三方库即可转换InputStream成的完整方法String。使用StringBuilder单线程环境以其它方式使用StringBuffer

public static String getString( InputStream is) throws IOException {
    int ch;
    StringBuilder sb = new StringBuilder();
    while((ch = is.read()) != -1)
        sb.append((char)ch);
    return sb.toString();
}

3
在这种方法中,没有应用编码。因此,假设从InputStream接收的数据是使用UTF-8编码的,则输出将是错误的。要解决此问题,您可以使用in = new InputStreamReader(inputStream)(char)in.read()
Frederic Leitenberger 2014年

2
以及内存效率低下;我相信我以前曾尝试在大型输入上使用此功能,而StringBuilder的内存
不足

1
还有另一个类似的答案,它使用char []缓冲区,效率更高,并且可以处理charset。
Guillaume Perrot 2015年

14

这是使用字节数组缓冲区仅使用JDK的方法。实际上,这就是commons-io IOUtils.copy()方法的全部工作方式。如果要从而不是进行复制byte[]char[]则可以替换ReaderInputStream

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

...

InputStream is = ....
ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
byte[] buffer = new byte[8192];
int count = 0;
try {
  while ((count = is.read(buffer)) != -1) {
    baos.write(buffer, 0, count);
  }
}
finally {
  try {
    is.close();
  }
  catch (Exception ignore) {
  }
}

String charset = "UTF-8";
String inputStreamAsString = baos.toString(charset);

1
请说明您要完成的工作。
拉古纳斯·贾瓦哈尔(Ragunath Jawahar)

14

Kotlin用户只需执行以下操作:

println(InputStreamReader(is).readText())

readText()

是Kotlin标准库的内置扩展方法。


这实际上不是很正确,因为它不会关闭流。我建议is.bufferedReader().use { it.readText() }
最高

9

JDK中最简单的方法是使用以下代码片段。

String convertToString(InputStream in){
    String resource = new Scanner(in).useDelimiter("\\Z").next();
    return resource;
}

7

这是我基于Java 8的解决方案,它使用新的Stream API来收集来自的所有行InputStream

public static String toString(InputStream inputStream) {
    BufferedReader reader = new BufferedReader(
        new InputStreamReader(inputStream));
    return reader.lines().collect(Collectors.joining(
        System.getProperty("line.separator")));
}

1
似乎您并未真正阅读之前发布的所有答案。Stream API版本已经存在至少两次了
塔吉尔·瓦列夫

我查看了所有解决方案,但发现不合适。我发现两行简短的描述是精确呈现的。例如,从不使用其他解决方案的try-catch-block。但是你是对的。有这么多答案,我切换到快速跳过阅读模式... :-)
ChristianRädel15

1
您不是在读取原始文件,而是将文件结尾的行转换为操作系统结尾的行,可能会更改文件内容。
Christian Hujer '16

7

在方面reduce,并且concat可以在Java中8表示为:

String fromFile = new BufferedReader(new   
InputStreamReader(inputStream)).lines().reduce(String::concat).get();

1
它将非常缓慢。
Tagir Valeev

有趣,为什么?你能详细说明吗?
libnull-dev

1
您不知道为什么以循环方式而不是使用StringBuilder串联字符串是一个坏主意吗?
塔吉尔·瓦列夫

你是对的。StringBuilder可能会更有效率。我会检查一下,但我的意思是要展示更多具有不变性的功能方法String
libnull-dev

功能方法很酷,但通常效率很低。
Lluis Martinez

4

JDK 7/8答案关闭了流,并且仍然抛出IOException:

StringBuilder build = new StringBuilder();
byte[] buf = new byte[1024];
int length;
try (InputStream is = getInputStream()) {
  while ((length = is.read(buf)) != -1) {
    build.append(new String(buf, 0, length));
  }
}
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.