You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 02:27:52