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


445

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

那么,给定an InputStream in和an OutputStream out,是否有更简单的方法编写以下内容?

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

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

Answers:


182

Java 9

从Java 9开始,InputStream提供了一种transferTo带有以下签名的方法:

public long transferTo(OutputStream out) throws IOException

文档所述,transferTo将:

从此输入流中读取所有字节,然后按读取顺序将字节写入给定的输出流。返回时,此输入流将在流的末尾。此方法不会关闭任何一个流。

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

因此,为了编写Java内容的InputStream一个OutputStream,你可以这样写:

input.transferTo(output);

11
您应该尽可能喜欢Files.copy。它以本机代码实现,因此可以更快。transferTo仅当两个流都不是FileInputStream / FileOutputStream时才应使用。
ZhekaKozlov

@ZhekaKozlov不幸的是,Files.copy它不处理任何输入/输出流,但它是专门为文件流设计的。
Impaler

396

正如WMR所述,org.apache.commons.io.IOUtilsApache提供了一种名为的方法copy(InputStream,OutputStream),该方法可以完全满足您的需求。

所以你有了:

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

...在您的代码中。

您有避免的理由IOUtils吗?


170
对于正在构建的移动应用程序,我避免使用它,因为它使应用程序的大小增加了五倍,以节省仅5行代码。
杰里米·洛根

36
也许值得一提的是inout必须在代码的结尾处的finally块中关闭它
basZero 2013年

23
@basZero或在资源块中尝试使用。
沃伦·露

1
或者,您也可以只写自己的副本(进出)包装器...(花更少的时间来写...)
MikeM 2014年

1
如果您已经在使用Guava库,Andrejs建议使用下面的ByteStreams类。与IOUtils相似,但是避免将Commons IO添加到项目中。
Jim Tough

328

如果您使用的是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()从文件获取路径。

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

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

26
我认为这实际上并不能解决问题,因为一端是道路。虽然您可以获取文件的路径,但据我所知,您无法为任何通用流获取路径(例如,通过网络获取的路径)。
马特·谢泼德

4
CopyOptions是任意的!如果需要,可以将其放在此处。
user1079877 2014年

4
现在就是我想要的!JDK进行救援,无需其他库
Don Cheadle 2014年

7
仅供参考,FilesAndroid的Java 1.7中不可用。我被这个事情ung住了:stackoverflow.com/questions/24869323/…–
约书亚·品特

23
有趣的是,JDK还具有一个Files.copy()接受两个流的,并且是所有其他Files.copy()功能为了进行实际复制工作而转发的功能。但是,它是私有的(因为在那个阶段它实际上并不涉及路径或文件),并且看起来完全像OP自己的问题中的代码(加上return语句)。没有打开,没有关闭,只是一个复制循环。
Ti Strga 2015年

102

我认为这会奏效,但请确保对其进行测试...较小的“改进”,但在可读性方面可能会有所付出。

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

26
我建议缓冲区至少为10KB到100KB。这并不多,并且可以极大地加快复制大量数据的速度。
亚伦·迪古拉

6
您可能要说while(len > 0)代替!= -1,因为后者在使用read(byte b[], int off, int len)-method 时也可能返回0 ,这会引发异常@out.write
phil294 2015年

12
@Blauhirn:那是不正确的,因为根据InputStream读取的合同,任何次数返回0 都是完全合法的。并且根据OutputStream合同,write方法必须接受长度为0的值,并且仅在len负数时抛出异常。
ChristofferHammarström

1
您可以通过将更while改为for并在for的init部分中放置一个变量来保存一行:例如,for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);。=)
ɲeuroburɳ

1
read()如果提供的长度为零,则@Blauhim 只能返回零,这将是编程错误,并且是一个永远循环的愚蠢条件。如果您提供的长度为零,write()不会引发异常。
罗恩侯爵,

54

使用番石榴的ByteStreams.copy()

ByteStreams.copy(inputStream, outputStream);

11
之后不要忘记关闭流!
WonderCsabo 2014年

如果您已经在使用Guava(对于我来说是必不可少的),这是最好的答案。

1
@Hong您应该Files.copy尽可能多地使用。使用ByteStreams.copy只有两个流都没有的FileInputStream / FileOutputStream中。
ZhekaKozlov

@ZhekaKozlov谢谢您的提示。就我而言,输入流来自Android应用程序的资源(可绘制)。

26

简单功能

如果只需要使用此函数将写入InputStream到,File则可以使用以下简单函数:

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
功能强大,谢谢。不过,您是否需要将close()通话分finally块进行?
Joshua Pinter

@JoshPinter不会受伤。
乔丹·拉普里斯

3
在实际的实现中,您可能都应同时包含finally块和非吞咽异常。而且,关闭传递给方法的InputStream有时对于调用方法来说是意外的,因此应该考虑这是否是他们想要的行为。
Cel Skeggs 2015年

2
当IOException足够时,为什么要捕获该异常?
Prabhakar

18

它们JDK使用相同的代码,因此,如果没有笨拙的第三方库,这似乎是没有“更轻松”的方式的(反正它们可能不会做任何不同的事情)。以下是直接从复制java.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

17

PipedInputStream并且PipedOutputStream应该只当你有多个线程使用,通过的Javadoc指出

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

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复制大量数据或流中无法忍受长时间卡住的数据,这将是一个有用的补充。


14

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

StreamUtils.copy(in, out);

上面没有关闭流。如果要在复制后关闭流,请使用FileCopyUtils类:

FileCopyUtils.copy(in, out);

8

有没有办法与JDK的方法来做到这一点轻松了许多,但Apocalisp已经指出的那样,你不是唯一一个有这样的想法:你可以使用IOUtilsJakarta Commons的IO,它也有很多其他有用的东西, IMO实际上应该成为JDK的一部分...


6

使用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();
}

3
循环内部冲洗会适得其反。
罗恩侯爵,

5

这就是我使用最简单的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);
    }
}

4

使用Commons Net的Util类:

import org.apache.commons.net.io.Util;
...
Util.copyStream(in, out);

3

一个恕我直言,更小的代码段(也更狭窄地限制长度变量):

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

顺便提一句,我不明白为什么更多的人不使用for循环,而是选择了while带有赋值和测试表达式的表达式,这种表达式被某些人认为是“不良”样式。


1
您的建议会导致在第一次迭代中写入0字节。也许至少是这样:for(int n = 0; (n = in.read(buffer)) > 0;) { out.write(buffer, 0, n); }
布赖恩·德·阿尔维斯

2
@BriandeAlwis您对第一次迭代不正确是正确的。该代码已得到修复(IMHO的使用方式比您的建议还干净)-请参见已编辑的代码。Thx的照顾。
波希米亚

3

这是我最好的镜头!

并且不要使用,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
    );
}

2

我认为最好使用大缓冲区,因为大多数文件都大于1024字节。另外,检查读取的字节数是否为正也是一个好习惯。

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

4
使用大缓冲区确实是个好主意,但不是因为文件大多> 1k,而是要摊销系统调用的成本。
2013年

1

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

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

为什么“从代码中删除缓冲语义”是一个好主意?
罗恩侯爵

2
这意味着我自己不写缓冲逻辑,而是使用内置在JDK中的缓冲逻辑,通常足够好。
Archimedes Trajano

0

PipedInputStream和PipedOutputStream可能有用,因为您可以将它们彼此连接。


1
这对单线程代码不利,因为它可能会死锁。看到这个问题stackoverflow.com/questions/484119/...
Raekye

2
可能会有什么用?他已经有一个输入流和一个输出流。如何在每个帮助中添加另一个帮助呢?
2013年

0

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

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

我以为我会使用它们,因为Guava在我的项目中已经非常有用,而不是为一个功能添加另一个库。


copytoByteArray的方法docs.guava-libraries.googlecode.com/git-history/release/javadoc/...(番石榴调用输入/输出流称为“字节流”和读/写器的“字符流”)
Raekye

如果您已经使用了番石榴库,那是个好主意,但是,如果不是,那是一个庞大的图书馆,它提供了数千种“与标准相去甚远”的方法。我会远离他们
rupps

“长毛象”?2.7MB,具有很少的依赖关系,并且提供一个API,该API会小心地避免重复核心JDK。
阿德里安·贝克

0

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

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

!= -1还是> 0?这些谓词并不完全相同。
Impaler

!= -1表示文件未结束。这不是迭代,而是伪装的while-do-loop:while((n = inputStream.read(buffer))!= -1)do {outputStream.write(buffer,0,n)}
IPP书呆子

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

4
您能解释一下为什么这是正确的答案吗?
rfornal 2015年


-6

你可以用这种方法

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
catch(Exception ex){}—这是一流的
UnclickableCharacter
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.