将 Java InputStream 的内容写入 OutputStream 的简单方法

Easy way to write contents of a Java InputStream to an OutputStream

提问人:Matt Sheppard 提问时间:9/4/2008 最后编辑:SledMatt Sheppard 更新时间:10/31/2021 访问量:494339

问:

今天我惊讶地发现,我无法找到任何简单的方法来在 Java 中将 an to an 的内容写入。显然,字节缓冲区代码并不难写,但我怀疑我只是缺少一些可以让我的生活更轻松(并且代码更清晰)的东西。InputStreamOutputStream

那么,给定 an 和 an ,有没有更简单的方法来写以下内容呢?InputStreaminOutputStreamout

byte[] buffer = new byte[1024];
int len = in.read(buffer);
while (len != -1) {
    out.write(buffer, 0, len);
    len = in.read(buffer);
}
Java IO

评论

1赞 Jabari 1/30/2016
您在评论中提到这是针对移动应用程序的。是原生 Android 吗?如果是这样,请告诉我,我会发布另一个答案(可以在 Android 中完成一行代码)。

答:

110赞 Mike Stone 9/4/2008 #1

我认为这会起作用,但请务必对其进行测试......轻微的“改进”,但在可读性方面可能会有点成本。

byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
    out.write(buffer, 0, len);
}

评论

27赞 Aaron Digulla 12/13/2008
我建议至少 10KB 到 100KB 的缓冲区。这并不多,可以大大加快复制大量数据的速度。
7赞 phil294 1/2/2015
您可能想说 而不是 ,因为后者在使用 -method 时也可能返回 0,这会引发异常 @while(len > 0)!= -1read(byte b[], int off, int len)out.write
13赞 Christoffer Hammarström 5/7/2015
@Blauhirn:这是不正确的,因为根据合同,读取返回 0 的次数是完全合法的。根据约定,write 方法必须接受长度为 0,并且只有在为负数时才应该抛出异常。InputStreamOutputStreamlen
1赞 ɲeuroburɳ 7/25/2015
您可以通过将 更改为 a 并将其中一个变量放在 for 的 init 部分来保存一行:例如,.=)whileforfor (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);
1赞 user207421 2/25/2016
@Blauhim 只有在提供长度为零时才能返回零,这将是一个编程错误,并且是一个永远循环的愚蠢条件。如果提供零长度,则不会引发异常。read()write()
0赞 Arktronic 9/4/2008 #2

PipedInputStream 和 PipedOutputStream 可能有一些用处,因为您可以将一个连接到另一个。

评论

1赞 Raekye 8/7/2013
这对单线程代码不利,因为它可能会死锁;看到这个问题 stackoverflow.com/questions/484119/...
2赞 user207421 10/30/2013
可能有一些用处,如何?他已经有一个输入流和一个输出流。添加另一个将如何帮助?
8赞 WMR 9/4/2008 #3

使用 JDK 方法没有办法更容易做到这一点,但正如 Apocalisp 已经指出的那样,您不是唯一一个有这个想法的人:您可以使用 Jakarta Commons IOIOUtils,它还有很多其他有用的东西,IMO 实际上应该成为 JDK 的一部分......

414赞 Mikezx6r 9/9/2008 #4

正如 WMR 所提到的,Apache 有一个名为 copy(InputStream,OutputStream) 的方法,它完全可以满足您的需求。org.apache.commons.io.IOUtils

所以,你有:

InputStream in;
OutputStream out;
IOUtils.copy(in,out);
in.close();
out.close();

...在您的代码中。

你回避是有原因的吗?IOUtils

评论

187赞 Jeremy Logan 8/29/2013
对于我正在构建的这个移动应用程序,我避免使用它,因为它会使应用程序的大小增加五倍,以节省区区 5 行代码。
24赞 Warren Dew 5/17/2014
@basZero 或者使用 try with resources 块。
1赞 Jim Tough 9/19/2014
如果您已经在使用 Guava 库,Andrejs 推荐了下面的 ByteStreams 类。与 IOUtils 类似,但避免将 Commons IO 添加到您的项目中。
0赞 Sled 7/23/2015
@fiXedd 您可以使用 Maven Shade 从最终.jar中剥离不需要的类,因此只会适度增加文件 jar 大小
0赞 Maarten Bodewes 3/11/2016
这很明显,但如果该类没有太多依赖项,您也可以简单地复制自由许可证的源代码(例如用于 Guava 和 Apache 的源代码)。首先阅读许可证(免责声明、IANAL 等)。
18赞 Dilum Ranatunga 2/12/2009 #5

PipedInputStream并且应该仅在有多个线程时使用,如 Javadoc 所述PipedOutputStream

另外,请注意,输入流和输出流不会将任何线程中断包装在...因此,应考虑将中断策略合并到代码中:IOException

byte[] buffer = new byte[1024];
int len = in.read(buffer);
while (len != -1) {
    out.write(buffer, 0, len);
    len = in.read(buffer);
    if (Thread.interrupted()) {
        throw new InterruptedException();
    }
}

如果您希望使用此 API 来复制大量数据,或者从卡住很长时间的流中复制数据,这将是一个有用的补充。

0赞 Andrew Mao 12/11/2012 #6

另一个可能的候选者是 Guava I/O 实用程序:

http://code.google.com/p/guava-libraries/wiki/IOExplained

我想我会使用这些,因为 Guava 在我的项目中已经非常有用,而不是为一个函数添加另一个库。

评论

0赞 Raekye 8/7/2013
docs.guava-libraries.googlecode.com/git-history/release/javadoc/ 中有和方法...(番石榴将输入/输出流称为“字节流”,将读取器/写入器称为“字符流”)copytoByteArray
0赞 rupps 11/28/2014
如果您已经使用番石榴库,这是一个好主意,但如果没有,它们是一个庞大的库,具有数千种方法“谷歌做一切与标准不同的方法”。我会远离他们
0赞 Adrian Baker 1/29/2020
“猛犸象”?2.7MB,具有非常小的依赖项集,以及一个小心避免复制核心 JDK 的 API。
2赞 Alexander Volkov 9/13/2013 #7

我认为最好使用大缓冲区,因为大多数文件都大于 1024 字节。此外,最好检查读取字节数为正数。

byte[] buffer = new byte[4096];
int n;
while ((n = in.read(buffer)) > 0) {
    out.write(buffer, 0, n);
}
out.close();

评论

4赞 user207421 10/30/2013
使用大缓冲区确实是一个好主意,但并不是因为文件大多> 1k,而是为了摊销系统调用的成本。
0赞 09Q71AO534 7/21/2020
如果我们使用此代码,可能会导致漏洞问题。最佳实践之一可在此处找到。请进行相应的修改。
31赞 Jordan LaPrise 9/14/2013 #8

功能简单

如果你只需要这个来写一个,那么你可以使用这个简单的函数:InputStreamFile

private void copyInputStreamToFile( InputStream in, File file ) {
    try {
        OutputStream out = new FileOutputStream(file);
        byte[] buf = new byte[1024];
        int len;
        while((len=in.read(buf))>0){
            out.write(buf,0,len);
        }
        out.close();
        in.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

评论

4赞 Joshua Pinter 1/25/2015
很棒的功能,谢谢。但是,您需要将呼叫放在块中吗?close()finally
0赞 Jordan LaPrise 1/26/2015
@JoshPinter 这不会有什么坏处。
3赞 Cel Skeggs 12/13/2015
您可能都应该在实际实现中包含 finally 块,而不是吞并异常。此外,关闭传递给方法的 InputStream 有时是调用方法意外的,因此应考虑这是否是他们想要的行为。
2赞 Prabhakar 1/14/2017
当 IOException 足够时,为什么要捕获异常?
0赞 09Q71AO534 7/21/2020
如果我们使用此代码,可能会导致漏洞问题。最佳实践之一可在此处找到。请进行相应的修改。
343赞 user1079877 10/5/2013 #9

如果您使用的是 Java 7,则文件(在标准库中)是最好的方法:

/* You can get Path from file also: file.toPath() */
Files.copy(InputStream in, Path target)
Files.copy(Path source, OutputStream out)

编辑:当然,当您从文件创建InputStream或OutputStream之一时,它才有用。用于从文件中获取路径。file.toPath()

要写入现有文件(例如,使用 创建的文件),您需要传递 copy 选项(否则会抛出):File.createTempFile()REPLACE_EXISTINGFileAlreadyExistsException

Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING)

评论

29赞 Matt Sheppard 10/7/2013
我不认为这实际上解决了问题,因为一端是一条路径。虽然您可以获取文件的路径,但据我所知,您无法获取任何通用流的路径(例如,通过网络获取)。
4赞 user1079877 7/7/2014
CopyOptions 是任意的!如果你愿意,你可以把它放在这里。
4赞 Don Cheadle 12/18/2014
现在这就是我一直在寻找的!JDK 来救援,无需其他库
9赞 Joshua Pinter 1/25/2015
仅供参考,在 Android 的 Java 1.7 中不可用。我被这个刺痛了:stackoverflow.com/questions/24869323/......Files
26赞 Ti Strga 9/10/2015
有趣的是,JDK 还有一个 which 需要两个流,并且是所有其他功能转发到的流,以便执行实际的复制工作。但是,它是私有的(因为它在那个阶段实际上不涉及路径或文件),并且看起来与 OP 自己的问题中的代码加上 return 语句)完全相同。没有打开,没有关闭,只是一个复制循环。Files.copy()Files.copy()
4赞 DejanLekic 11/12/2013 #10

使用 Commons Net 的 Util 类:

import org.apache.commons.net.io.Util;
...
Util.copyStream(in, out);
-7赞 Pranav 3/6/2014 #11

您可以使用此方法

public static void copyStream(InputStream is, OutputStream os)
 {
     final int buffer_size=1024;
     try
     {
         byte[] bytes=new byte[buffer_size];
         for(;;)
         {
           int count=is.read(bytes, 0, buffer_size);
           if(count==-1)
               break;
           os.write(bytes, 0, count);
         }
     }
     catch(Exception ex){}
 }

评论

6赞 ᄂ ᄀ 7/25/2017
catch(Exception ex){}— 这是一流的
58赞 Andrejs 3/26/2014 #12

使用 Guava 的 ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);

评论

12赞 WonderCsabo 11/8/2014
之后不要忘记关闭溪流!
0赞 Hong 3/30/2018
如果您已经在使用番石榴,这是最好的答案,这对我来说已经不可或缺。
1赞 ZhekaKozlov 6/7/2018
@Hong 您应该尽可能多地使用。仅当两个流都不是 FileInputStream/FileOutputStream 时才使用。Files.copyByteStreams.copy
-1赞 Nour Rteil 4/3/2015 #13
public static boolean copyFile(InputStream inputStream, OutputStream out) {
    byte buf[] = new byte[1024];
    int len;
    long startTime=System.currentTimeMillis();

    try {
        while ((len = inputStream.read(buf)) != -1) {
            out.write(buf, 0, len);
        }

        long endTime=System.currentTimeMillis()-startTime;
        Log.v("","Time taken to transfer all bytes is : "+endTime);
        out.close();
        inputStream.close();

    } catch (IOException e) {

        return false;
    }
    return true;
}

评论

5赞 rfornal 4/3/2015
您能解释一下为什么这是正确的答案吗?
8赞 Sivakumar 8/21/2015 #14

使用 Java7 和 try-with-resources,附带了一个简化且可读的版本。

try(InputStream inputStream = new FileInputStream("C:\\mov.mp4");
    OutputStream outputStream = new FileOutputStream("D:\\mov.mp4")) {

    byte[] buffer = new byte[10*1024];

    for (int length; (length = inputStream.read(buffer)) != -1; ) {
        outputStream.write(buffer, 0, length);
    }
} catch (FileNotFoundException exception) {
    exception.printStackTrace();
} catch (IOException ioException) {
    ioException.printStackTrace();
}

评论

4赞 user207421 2/25/2016
在回路内冲洗会适得其反。
3赞 Bohemian 12/10/2015 #15

恕我直言,一个更小的片段(也更狭窄地限定了长度变量的范围):

byte[] buffer = new byte[2048];
for (int n = in.read(buffer); n >= 0; n = in.read(buffer))
    out.write(buffer, 0, n);

顺便说一句,我不明白为什么越来越多的人不使用循环,而是选择带有赋值和测试表达式的表达式,这被一些人认为是“糟糕”的风格。forwhile

评论

1赞 Brian de Alwis 2/8/2016
您的建议会导致在第一次迭代时写入 0 字节。也许至少要做:for(int n = 0; (n = in.read(buffer)) > 0;) { out.write(buffer, 0, n); }
2赞 Bohemian 2/8/2016
@BriandeAlwis 你说的第一次迭代是不正确的。代码已修复(恕我直言,比您的建议更干净) - 请参阅编辑后的代码。谢谢关心。
6赞 Jin Kwon 8/22/2016 #16

这是我如何使用最简单的 for 循环。

private void copy(final InputStream in, final OutputStream out)
    throws IOException {
    final byte[] b = new byte[8192];
    for (int r; (r = in.read(b)) != -1;) {
        out.write(b, 0, r);
    }
}
244赞 Ali Dehghani 9/12/2016 #17

Java 9 (英语)

从 Java 9 开始,提供了一个使用以下签名调用的方法:InputStreamtransferTo

public long transferTo(OutputStream out) throws IOException

文档所述,将:transferTo

从此输入流中读取所有字节,并将这些字节写入 按读取顺序给定输出流。返回时,这 输入流将位于流的末尾。此方法不会关闭 任一流。

此方法可能会无限期地阻止从 输入流,或写入输出流。的行为 输入和/或输出流异步关闭的情况,或 在传输过程中中断的线程,是高度输入和输出的 特定于流,因此未指定

因此,为了将 Java 的内容写入 ,您可以编写:InputStreamOutputStream

input.transferTo(output);

评论

18赞 ZhekaKozlov 6/7/2018
你应该尽可能多地选择。它是用本机代码实现的,因此可以更快。 仅当两个流都不是 FileInputStream/FileOutputStream 时才应使用。Files.copytransferTo
5赞 The Impaler 4/10/2020
@ZhekaKozlov 不幸的是,它不处理任何输入/输出流,但它是专门为文件流设计的。Files.copy
1赞 Prof 4/6/2021
也仅适用于 >API 26
4赞 Ali Dehghani 8/16/2021
@ZhekaKozlov Files.copy(in, out) 似乎也在幕后使用该方法。因此,似乎没有原生代码,除非 JVM 提供了transferToFiles.copy(in, out)
21赞 BullyWiiPlaza 10/13/2016 #18

它使用相同的代码,因此如果没有笨拙的第三方库(无论如何,它们可能不会做任何不同的事情),似乎没有“更简单”的方法。以下内容直接复制自:JDKjava.nio.file.Files.java

// buffer size used for reading and writing
private static final int BUFFER_SIZE = 8192;

/**
  * Reads all bytes from an input stream and writes them to an output stream.
  */
private static long copy(InputStream source, OutputStream sink) throws IOException {
    long nread = 0L;
    byte[] buf = new byte[BUFFER_SIZE];
    int n;
    while ((n = source.read(buf)) > 0) {
        sink.write(buf, 0, n);
        nread += n;
    }
    return nread;
}

评论

2赞 Dragas 9/13/2018
赞成。遗憾的是,这个特定的调用是私有的,除了将其复制到您自己的实用程序类中之外别无选择,因为您可能不是在处理文件,而是同时处理 2 个套接字。
21赞 holmis83 2/2/2017 #19

对于那些使用 Spring 框架的人来说,有一个有用的 StreamUtils 类:

StreamUtils.copy(in, out);

以上内容不会关闭流。如果要在复制后关闭流,请改用 FileCopyUtils 类:

FileCopyUtils.copy(in, out);
-1赞 yegor256 9/22/2017 #20

试试 Cactoos

new LengthOf(new TeeInput(input, output)).value();

更多详情请见:http://www.yegor256.com/2017/06/22/object-oriented-input-output-in-cactoos.html

4赞 Archimedes Trajano 1/20/2019 #21

我使用 和 从代码中删除缓冲语义BufferedInputStreamBufferedOutputStream

try (OutputStream out = new BufferedOutputStream(...);
     InputStream in   = new BufferedInputStream(...))) {
  int ch;
  while ((ch = in.read()) != -1) {
    out.write(ch);
  }
}

评论

1赞 user207421 1/28/2020
为什么“从代码中删除缓冲语义”是个好主意?
3赞 Archimedes Trajano 1/28/2020
这意味着我不会自己编写缓冲逻辑,我使用JDK内置的逻辑,这通常已经足够好了。
3赞 Daniel De León 7/11/2019 #22

这是我最好的镜头!!

并且不要使用 inputStream.transferTo(...),因为它太泛型了。如果控制缓冲区内存,则代码性能会更好。

public static void transfer(InputStream in, OutputStream out, int buffer) throws IOException {
    byte[] read = new byte[buffer]; // Your buffer size.
    while (0 < (buffer = in.read(read)))
        out.write(read, 0, buffer);
}

当我事先知道流的大小时,我会将它与这种(可改进的)方法一起使用。

public static void transfer(int size, InputStream in, OutputStream out) throws IOException {
    transfer(in, out,
            size > 0xFFFF ? 0xFFFF // 16bits 65,536
                    : size > 0xFFF ? 0xFFF// 12bits 4096
                            : size < 0xFF ? 0xFF // 8bits 256
                                    : size
    );
}

评论

1赞 Garret Wilson 11/25/2020
“并且不要使用 inputStream.transferTo(...)因为太泛型了。如果你控制你的缓冲区内存,你的代码性能会更好。这听起来很有道理,事实上,我自己的代码最初试图根据已知的传输大小来选择缓冲区大小。但我正在阅读答案可能更复杂,部分原因是基于驱动器块大小和 CPU 缓存。您是否做过任何实际测试来支持您的说法,即自定义缓冲区大小的性能优于 ?如果是这样,我很想看看他们。性能很棘手。InputStream.transferTo(OutputStream)
0赞 Unmitigated 3/27/2021
你真的看到过是如何实现的吗?transferTo
2赞 IPP Nerd 3/21/2020 #23

可读性不是很强,但很有效,没有依赖关系,可以与任何 java 版本一起运行

byte[] buffer=new byte[1024];
for(int n; (n=inputStream.read(buffer))!=-1; outputStream.write(buffer,0,n));

评论

1赞 The Impaler 4/10/2020
!= -1或?这些谓词并不完全相同。> 0
0赞 IPP Nerd 4/11/2020
!= -1 表示非文件末尾。这不是一个迭代,而是一个伪装的while-do-loop: while((n = inputStream.read(buffer)) != -1) do { outputStream.write(buffer, 0,n) }
0赞 fullmoon 7/23/2021 #24

我用的方法ByteStreamKt.copyTo(src, dst, buffer.length)

这是我的代码

public static void replaceCurrentDb(Context context, Uri newDbUri) {
    try {
        File currentDb = context.getDatabasePath(DATABASE_NAME);
        if (currentDb.exists()) {
            InputStream src = context.getContentResolver().openInputStream(newDbUri);
            FileOutputStream dst = new FileOutputStream(currentDb);
            final byte[] buffer = new byte[8 * 1024];
            ByteStreamsKt.copyTo(src, dst, buffer.length);
            src.close();
            dst.close();
            Toast.makeText(context, "SUCCESS! Your selected file is set as current menu.", Toast.LENGTH_LONG).show();
        }
        else
            Log.e("DOWNLOAD:::: Database", " fail, database not found");
    }
    catch (IOException e) {
        Toast.makeText(context, "Data Download FAIL.", Toast.LENGTH_LONG).show();
        Log.e("DOWNLOAD FAIL!!!", "fail, reason:", e);
    }
}