将Java输入流的内容写入输出流的简单方法

我惊讶地发现,今天我找不到任何简单的方法来在Java中将InputStream的内容写入OutputStream。显然,编写字节缓冲区代码并不难,但我怀疑我只是缺少了一些可以使我的工作更简单(并且代码更清晰)的东西。

因此,给定InputStream inOutputStream out,是否有更简单的方法来编写下面的代码?

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

我认为这是可行的,但一定要测试一下……轻微的“改进”,但可能会以可读性为代价。

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

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

JDK方法没有办法更容易做到这一点,但正如Apocalisp已经指出的,你不是唯一一个有这个想法的人:你可以使用IOUtilsJakarta Commons IO,它也有很多其他有用的东西,我认为实际上应该是JDK的一部分……

正如WMR提到的,Apache中的org.apache.commons.io.IOUtils有一个名为copy(InputStream,OutputStream)的方法,它所做的正是你所寻找的。

所以,你有:

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

…在你的代码中。

有什么原因让你避免IOUtils吗?

PipedInputStreamPipedOutputStream只能在有多个线程时使用,如由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复制大量数据,或者从流中复制长时间无法忍受的数据,那么这将是一个有用的补充。

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

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

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

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

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

简单的函数

如果你只需要将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();
}
}

如果你使用的是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_EXISTING复制选项(否则会抛出FileAlreadyExistsException):

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

使用Commons Net的Util类:

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

你可以使用这种方法

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){}
}

使用Guava的ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);
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;
}

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

一个IMHO更小的代码片段(也更狭窄地作用于长度变量):

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

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

下面是我如何使用最简单的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);
}
}

Java 9

自Java 9以来,InputStream提供了一个名为transferTo的方法,具有以下签名:

public long transferTo(OutputStream out) throws IOException

正如文档所述,transferTo将:

从输入流中读取所有字节,并将字节写入 给定的输出流,以读取它们的顺序。返回时,这个 输入流将位于流的末尾。此方法不会关闭 要么流。< / p > 对象的读取可能会无限期地阻塞 输入流,或写入输出流。的行为 输入和/或输出流异步关闭的情况,或者 线程中断传输期间,是高输入和高输出 流特定的,因此没有指定

因此,为了将Java InputStream的内容写入OutputStream,你可以这样写:

input.transferTo(output);

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

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

StreamUtils.copy(in, out);

上面没有关闭流。如果你想在复制之后关闭流,可以使用FileCopyUtils类:

FileCopyUtils.copy(in, out);

试试Cactoos:

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

更多细节:http://www.yegor256.com/2017/06/22/object-oriented-input-output-in-cactoos.html

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

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

这是我最好的机会!!

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

可读性不是很好,但是很有效,没有依赖关系,可以在任何java版本上运行

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

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