Java大文件复制时如何可靠监控进度?现有方案存问题
Java中监控文件复制进度的可靠方案
问题背景
我目前使用Files.copy(new File(source).toPath(), new File(target).toPath(), StandardCopyOption.REPLACE_EXISTING);进行文件复制,但需要监控复制进度,尝试过两种方案都有缺陷:
方案1:并行线程检查目标文件大小
通过单独线程轮询目标文件大小计算进度,但存在两个明显问题:
- 有时会锁定目标文件,导致复制流程异常中断
- 可能在复制完成前就显示文件已达到完整大小(部分操作系统会提前为目标文件预分配空间)
对应的实现代码:
Thread one = new Thread() { public void run() { System.out.println("Thread start"); while (true) { try { File sourceFile = new File(target); if (sourceFile.exists()) { long bytes = Files.size(sourceFile.toPath()); double length = sourceFile.length()*1.0; System.out.println(1.0 * length / sourcebytes); } else { System.out.println("Source file does not exist"); } Thread.sleep(100); // 休眠100毫秒 } catch (InterruptedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } }; one.start();
方案2:手动流复制并监控进度
通过FileInputStream和FileOutputStream手动读写字节,同时统计已复制字节数计算进度,但复制速度太慢,无法满足性能需求。
对应的实现代码:
File filein = new File(source); File fileout = new File(target); FileInputStream fin = null; FileOutputStream fout = null; long length = filein.length(); long counter = 0; int r = 0; byte[] b = new byte[1024]; int lastProgress = -1; // 初始化为不可能的进度值 try { fin = new FileInputStream(filein); fout = new FileOutputStream(fileout); while ((r = fin.read(b)) != -1) { counter += r; if (counter % 1024 == 0) { int progress = (int) Math.round((double) counter / length * 100); if (progress > lastProgress) { // 仅当进度变化时打印 System.out.println(progress + "%"); lastProgress = progress; } } fout.write(b, 0, r); } } catch (Exception e) { System.out.println("foo"); }
推荐解决方案
1. 基于NIO通道的带进度回调方案
利用Java NIO的FileChannel实现高效复制(Files.copy底层也是基于此实现),同时嵌入进度监控逻辑,既保留原生高性能,又能精准获取实时进度,不会出现预分配空间导致的进度误差,也不会锁定目标文件。
示例代码:
import java.io.File; import java.io.IOException; import java.nio.channels.FileChannel; import java.nio.file.StandardOpenOption; public class ProgressFileCopy { // 进度回调接口 public interface ProgressCallback { void onProgress(long copiedBytes, long totalBytes); } public static void copyFileWithProgress(File source, File target, ProgressCallback callback) throws IOException { try (FileChannel sourceChannel = FileChannel.open(source.toPath(), StandardOpenOption.READ); FileChannel targetChannel = FileChannel.open(target.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) { long totalBytes = sourceChannel.size(); long copiedBytes = 0; // 每次传输8MB,平衡性能和进度更新频率 final int TRANSFER_BUFFER_SIZE = 8 * 1024 * 1024; while ((copiedBytes += sourceChannel.transferTo(copiedBytes, TRANSFER_BUFFER_SIZE, targetChannel)) > 0) { callback.onProgress(copiedBytes, totalBytes); } // 确保最后触发100%进度回调 callback.onProgress(totalBytes, totalBytes); } } // 使用示例 public static void main(String[] args) { File source = new File("source.txt"); File target = new File("target.txt"); try { copyFileWithProgress(source, target, (copied, total) -> { int progress = (int) Math.round((double) copied / total * 100); System.out.printf("复制进度:%d%%%n", progress); }); } catch (IOException e) { e.printStackTrace(); } } }
2. 优化后的流复制方案
如果坚持使用流方式,可通过以下优化大幅提升性能:
- 增大缓冲区大小(比如改为8MB或16MB,而非1KB)
- 使用
BufferedInputStream和BufferedOutputStream包装原始流 - 减少进度更新频率(比如每复制1MB更新一次,而非每1KB)
优化后的示例代码:
import java.io.*; public class OptimizedStreamCopy { public interface ProgressCallback { void onProgress(long copiedBytes, long totalBytes); } public static void copyWithProgress(File source, File target, ProgressCallback callback) throws IOException { long totalBytes = source.length(); long copiedBytes = 0; // 8MB缓冲区 byte[] buffer = new byte[8 * 1024 * 1024]; int bytesRead; try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(source)); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(target))) { while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); copiedBytes += bytesRead; // 每复制1MB更新一次进度,减少回调开销 if (copiedBytes % (1024 * 1024) == 0 || copiedBytes == totalBytes) { callback.onProgress(copiedBytes, totalBytes); } } out.flush(); } } public static void main(String[] args) { File source = new File("source.txt"); File target = new File("target.txt"); try { copyWithProgress(source, target, (copied, total) -> { int progress = (int) Math.round((double) copied / total * 100); System.out.printf("复制进度:%d%%%n", progress); }); } catch (IOException e) { e.printStackTrace(); } } }
方案优势对比
- NIO通道方案:性能与
Files.copy几乎一致,进度监控精准,无文件锁定和预分配空间的问题,是首选方案。 - 优化后的流方案:性能接近NIO水平,适合需要兼容旧版Java或自定义流处理逻辑的场景。
内容的提问来源于stack exchange,提问作者Sünüür
相关产品推荐
相关产品推荐

