题 读取/将InputStream转换为String


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


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

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

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

3267
2017-11-21 16:47


起源


男孩,我完全爱上了Java,但这个问题常常出现,你会认为他们只是想知道流的链接有点困难,要么帮助创建各种组合或重新思考整个事物。 - Bill K
只有在您想要阅读流的内容时,此问题的答案才有效 充分 (直到它关闭)。由于并非总是这样(具有保持活动连接的http请求不会被关闭),因此这些方法会调用阻塞(不提供内容)。 - f1sh
您 需要 知道并指定流的字符编码,或者您 将 有字符编码错误,因为您将使用随机选择的编码,具体取决于运行代码的机器/操作系统/平台或版本。就是这样 不 使用依赖于平台默认编码的方法。 - Christoffer Hammarström
为了享受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


答案:


一个很好的方法是使用 Apache公地  IOUtils 复制 InputStream 变成一个 StringWriter... 就像是

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


2048
2017-11-21 16:54



我发现filenotfound异常,当我尝试用“До_свидания”文件名(俄语)读取文件名时我尝试使用FileInputstream但是没有电缆从sdcard读取此文件名。 - Bhanu Sharma
对于Android开发人员来说,似乎android没有来自Apache的IOUtils。所以你可以考虑参考其他答案。 - Chris.Zou
我在有限的环境中工作,所以@PavelRepin下面的解决方案使用java io / util libs更有意义。 - James
这是一个令人难以置信的老问题(2008年被问到)。值得花时间阅读更现代的答案。一些使用来自Java 8库的本机调用。 - Shadoninja
这个答案已经过时了,应该能够将其标记为这样(遗憾的是,这不可能是atm)。 - codepleb


这是一种仅使用标准Java库的方法(注意流未关闭,YMMV)。

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

我从中学到了这个技巧 “愚蠢的扫描仪技巧” 文章。它起作用的原因是因为 扫描器 迭代流中的标记,在这种情况下,我们使用“输入边界的开头”(\ A)分隔标记,从而只为流的整个内容提供一个标记。

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

帽子小贴士也去了 雅各 曾经把我指向上述文章的人。

编辑: 感谢你的建议 帕特里克,在处理空输入流时使函数更加健壮。 还有一个编辑: nixed try / catch,帕特里克的方式更简洁。


2093
2018-03-26 20:40



谢谢,对于我的这个版本,我添加了一个关闭输入流的finally块,所以用户没有必要,因为你已经读完了输入。大大简化了调用者代码。
在我的案例中@PavelRepin @Patrick,一个空的inputStream在Scanner构造期间导致了NPE。我不得不补充一下 if (is == null) return ""; 就在方法的开头;我相信这个答案需要更新以更好地处理null inputStreams。 - CFL_Jeff
对于Java 7,您可以尝试关闭: try(java.util.Scanner s = new java.util.Scanner(is)) { return s.useDelimiter("\\A").hasNext() ? s.next() : ""; } - earcam
不幸的是,这个解决方案似乎失去了我的底层流实现中抛出的异常。 - Taig
仅供参考, hasNext 控制台输入流上的块(参见 这里)。 (刚刚遇到这个问题。)这个解决方案工作正常,否则......只是一个抬头。 - Ryan


总结其他答案我找到了11种主要方法(见下文)。我写了一些性能测试(见下面的结果):

将InputStream转换为String的方法:

  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. 运用 流API (Java 8)。 警告:此解决方案转换不同的换行符(如 \r\n) 至 \n

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

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

    final int bufferSize = 1024;
    final char[] buffer = new char[bufferSize];
    final StringBuilder out = new StringBuilder();
    Reader in = new InputStreamReader(inputStream, "UTF-8");
    for (; ; ) {
        int rsz = in.read(buffer, 0, buffer.length);
        if (rsz < 0)
            break;
        out.append(buffer, 0, rsz);
    }
    return out.toString();
    
  7. 运用 StringWriter 和 IOUtils.copy (Apache Commons)

    StringWriter writer = new StringWriter();
    IOUtils.copy(inputStream, writer, "UTF-8");
    return writer.toString();
    
  8. 运用 ByteArrayOutputStream 和 inputStream.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();
    String line; boolean flag = false;
    while ((line = reader.readLine()) != null) {
        result.append(flag? newLine: "").append(line);
        flag = true;
    }
    return result.toString();
    
  10. 运用 BufferedInputStream 和 ByteArrayOutputStream (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上 (模式=平均时间,系统= 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上 (模式=平均时间,系统= 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系统中的输入流长度)
enter image description here

性能测试(平均时间)取决于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

1656
2018-02-17 00:58



在您撰写“摘要答案”时,您应该注意到某些解决方案会自动转换不同的换行符(例如 \r\n) 至 \n 在某些情况下可能不受欢迎。也很高兴看到所需的额外内存或至少是分配压力(至少你可以运行JMH) -prof gc)。对于非常酷的帖子,很高兴看到图形(取决于相同输入大小内的字符串长度,并取决于相同字符串长度内的输入大小)。 - Tagir Valeev
Upvoted;最有趣的是结果超出预期:应该使用标准的JDK和/或Apache Commons语法糖。 - mudasobwa
惊人的帖子。就一件事。 Java 8警告不要在资源上使用并行流来强制你锁定和等待(例如这个输入流),所以并行流选项相当麻烦而且不值得吗? - mangusbrother
并行流实际上是否保持行顺序? - Natix
什么是 reset() 在例11中? - Rob Stewart


Apache Commons允许:

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

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

另见:(文件


794
2017-12-08 20:13



此外,如果您使用默认编码找到,则只有一个方法只接受inputStream参数。 - Guillaume Coté
@GuillaumeCoté我想这里的消息是你永远不应该“使用默认编码”,因为你不能确定它是什么,这取决于运行java代码的平台。 - Per Wiklander
@Per Wiklander我不同意你的意见。对单个代码起作用的代码可以确定默认编码没问题。对于仅打开本地文件的代码,要求它们以平台默认编码进行编码是合理的选择。 - Guillaume Coté
为了节省任何人Googling的麻烦 - <dependency> <groupId> org.apache.commons </ groupId> <artifactId> commons-io </ artifactId> <version> 1.3.2 </ version> </ dependency> - Chris
使用apache io(或其他)常量进行字符编码而不是使用普通字符串文字也很少有改进 - 例如:IOUtils.toString(myInputStream,Charsets.UTF_8);


考虑到文件一应该首先得到一个 java.io.Reader 实例。然后可以将其读取并添加到a StringBuilder (我们不需要 StringBuffer 如果我们没有在多个线程中访问它,并且 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();
}

263
2017-08-04 08:29



此解决方案使用多字节字符。该示例使用UTF-8编码,该编码允许表达完整的unicode范围(包括中文)。用另一种编码替换“UTF-8”将允许使用该编码。 - Paul de Vrieze
@ User1 - 我喜欢在我的代码中使用库,这样我就可以更快地完成工作。当你的经理说“哇詹姆斯!你怎么这么快就完成了这件事?”时,这真是太棒了。但是,当我们不得不花时间重新发明轮子只是因为我们错误地提出了包含一个共同的,可重复使用的,经过试验和测试的实用程序的想法,我们放弃了时间,我们可以花费更多时间来进一步实现我们项目的目标。当我们重新发明轮子时,我们努力工作两倍,但很晚才到达终点线。一旦我们到达终点线,没有人在那里向我们表示祝贺。在建造房屋时,也不要制造锤子 - jmort253
对不起,在重新阅读我的评论后,它有点傲慢。我认为有充分的理由避免使用库是很重要的,原因是有效的,很可能是:) - jmort253
@jmort253在我们的产品中多次更新某些库后,我们注意到了性能回归。幸运的是,我们正在建立和销售自己的产品,所以我们并没有真正拥有所谓的最后期限。不幸的是,我们正在构建一个可在许多操作系统上的许多JVM,数据库和应用服务器上使用的产品,因此我们必须考虑使用不良机器的用户......并且字符串操作优化可以将性能提高30%~40%。并修复: In our product, I even replaced 应该是'我们甚至更换'。 - coolcfan
@ jmort253如果你已经使用了apache commons我会说,去吧。同时,使用库会产生实际成本(因为许多apache java库中的依赖性增长显示)。如果这是图书馆的唯一用途,那么使用图书馆就太过分了。另一方面,确定您自己的缓冲区大小可以调整内存/处理器使用率平衡。 - Paul de Vrieze


这个怎么样?

InputStream in = / *你的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();

226
2017-07-13 15:56



问题是,你首先分成几行,然后撤消它。只读取任意缓冲区更容易,更快捷。 - Paul de Vrieze
此外,readLine不区分\ n和\ r,因此您无法再次重现确切的流。 - María Arias de Reyna Domínguez
@PauldeVrieze有多少行,你需要多快处理它们!?我猜测任何性能损失都会很小,或者每隔一段时间就可以将它们记录到一个文件并销毁旧的String obj。 - Thufir
非常低效,如 readLine 逐个字符地阅读以寻找EOL。此外,如果流中没有换行符,这实际上没有意义。 - njzk2
这不是最好的答案,因为它不是字节输出的严格字节。读者选择换行,所以你必须小心维护它们。 - Jeffrey Blattman


如果您使用的是Google-Collections / Guava,则可以执行以下操作:

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

注意第二个参数(即Charsets.UTF_8) InputStreamReader 没有必要,但如果您知道它(通常应该!),通常最好指定编码。


153
2018-05-08 20:24



@harschware:问题是:“如果你有java.io.InputStream对象,你应该如何处理该对象并生成一个String?”我假设情况中已经存在流。 - Sakuraba
你没有很好地解释你的答案,并且有无关的变量; user359996和你说的一样,但更清楚。 - Uronym
guava为+1,-1为未指定输入流的编码。例如。 new InputStreamReader(stream,“UTF-8”) - andras
@Chris Noldus另一方面,有些人已经在他们的项目中有像我一样的番石榴,并认为这个解决方案比仅限sdk的版本更优雅。 - CorayThan
@Vadzim答案与此答案相同 - 都使用CharStreams.toString - Tom