复制二进制数据
我正在尝试读取视频文件,并将其写入另一个文件,但由于某种原因,当我进行 md5sum 比较时,它们不同,并且 VLC 无法读取新文件。想法?
原始文件的
MD5 总和:b13d9acecd2dd3f869245c8e085f88c2 新文件的 MD5 总和:d41d8cd98f00b204e9800998ecf8427e
public void copyFile() throws IOException {
BufferedInputStream in = new BufferedInputStream(new FileInputStream("/home/zevrant/tmp/test.h264"));
BufferedOutputStream fileOutputStream = new BufferedOutputStream(new FileOutputStream(new File("/security/footage/test.h264")));
while(in.available() > 0) {
bytes = in.readNBytes(in.available());
fileOutputStream.write(bytes);
fileOutputStream.flush();
}
}
回答
你的copyFile方法的根本问题是:
while (in.available() > 0) {
这是一个问题,因为in.available()返回当前可以在不阻塞的情况下从输入流中读取的字节数。如果数据的消费者“赶上”了生产者,那可能是零字节,即使您还没有到达流的末尾:
-
对于套接字,如果远程服务器或网络速度较慢,或者网络中出现“打嗝”,就会发生这种情况。
-
对于管道,如果管道另一端的程序不能足够快地写入数据,就会发生这种情况。
-
对于常规文件(或文件共享上的文件),可能会发生这种情况,文件系统预读无法在缓存中保留足够的数据。(文件的实际行为
available()取决于操作系统和文件系统类型。做出假设是不明智的......)
如果发生这种情况,您的方法可能会错误地假设它已到达结尾,并在复制整个流之前关闭流......。
如果您要“手动”复制流,正确的编写方法如下:
public static void copyFile() throws IOException {
try (
InputStream in = new FileInputStream(...));
OutputStream out = new FileOutputStream(new File(...))) {
byte[] bytes = new byte[8192]; // Or use a larger buffer.
int nosRead;
while ((nosRead = in.read(bytes)) > 0) {
out.write(bytes, 0, nosRead);
}
}
}
笔记:
-
我们
available()不习惯决定阅读多少。我们读取多达 8192 字节的块。(这个数字可能更大,但使缓冲区太大也有缺点。) -
无需为此使用缓冲流。我们正在做我们自己的(简单的)缓冲。
-
我们需要确保文件已关闭。一个有资源的尝试是要做到这一点(从Java 7起)的最佳方式。资源(
in和out)将自动关闭。(并且关闭会导致冲洗。) -
也可以使用
ChannelandByteBuffer,这可能会更快。
但是如果你将一个文件复制到一个文件中,那么 Java8Files.copy(...)会更简单,也更高效(可能是最高效的)。所有的复制都是在幕后完成的,可以合理地假设它将以最佳方式完成(对于 Java)。
在 Java 9 中,您还可以选择使用InputStream.transferTo(...)which 来利用特殊的 I/O 系统调用,当您将数据从一个“文件描述符”传输到另一个“文件描述符”时,这些调用可以减少内存到内存的复制。