=== 单线程复制开始 ===
Starting copy, total size: 8.62 GB
[= ] 5.0% (444.7 MB/8.62 GB) | Speed: 33.5 MB/s | ETA: 4m 10s单线程进度: 50%
[= ] 6.0% (530.1 MB/8.62 GB) | Speed: 53.1 MB/s | ETA: 2m 36s单线程进度: 60%
[= ] 7.0% (623.0 MB/8.62 GB) | Speed: 64.0 MB/s | ETA: 2m 8s单线程进度: 70%
[= ] 8.0% (706.4 MB/8.62 GB) | Speed: 32.0 MB/s | ETA: 4m 13s单线程进度: 80%
[== ] 10.5% (926.9 MB/8.62 GB) | Speed: 46.0 MB/s | ETA: 2m 51s单线程进度: 105%
[== ] 14.5% (1.25 GB/8.62 GB) | Speed: 52.5 MB/s | ETA: 2m 23s单线程进度: 145%
[=== ] 17.0% (1.46 GB/8.62 GB) | Speed: 5000.0 MB/s | ETA: 1s单线程进度: 170%
[=== ] 17.5% (1.51 GB/8.62 GB) | Speed: 65.6 MB/s | ETA: 1m 50s单线程进度: 175%
[=== ] 18.0% (1.56 GB/8.62 GB) | Speed: 5000.0 MB/s | ETA: 1s单线程进度: 180%
[=== ] 18.5% (1.59 GB/8.62 GB) | Speed: 5000.0 MB/s | ETA: 1s单线程进度: 185%
[==== ] 20.0% (1.72 GB/8.62 GB) | Speed: 12.7 MB/s | ETA: 9m 14s单线程进度: 200%
[==== ] 20.5% (1.77 GB/8.62 GB) | Speed: 65.1 MB/s | ETA: 1m 47s单线程进度: 205%
[==== ] 21.0% (1.81 GB/8.62 GB) | Speed: 5000.0 MB/s | ETA: 1s单线程进度: 210%
[==== ] 21.5% (1.85 GB/8.62 GB) | Speed: 5000.0 MB/s | ETA: 1s单线程进度: 215%
[==== ] 22.0% (1.90 GB/8.62 GB) | Speed: 35.6 MB/s | ETA: 3m 13s单线程进度: 220%
[==== ] 22.5% (1.94 GB/8.62 GB) | Speed: 22.7 MB/s | ETA: 5m 1s单线程进度: 225%
[==== ] 24.5% (2.11 GB/8.62 GB) | Speed: 164.3 MB/s | ETA: 40s单线程进度: 245%
[===== ] 25.0% (2.15 GB/8.62 GB) | Speed: 285.8 MB/s | ETA: 23s单线程进度: 250%
[===== ] 27.0% (2.33 GB/8.62 GB) | Speed: 183.5 MB/s | ETA: 35s单线程进度: 270%
[===== ] 27.5% (2.37 GB/8.62 GB) | Speed: 95.7 MB/s | ETA: 1m 6s单线程进度: 275%
[===== ] 28.0% (2.42 GB/8.62 GB) | Speed: 104.2 MB/s | ETA: 1m 0s单线程进度: 280%
[===== ] 29.0% (2.51 GB/8.62 GB) | Speed: 65.9 MB/s | ETA: 1m 34s单线程进度: 290%
[===== ] 29.5% (2.54 GB/8.62 GB) | Speed: 99.4 MB/s | ETA: 1m 2s单线程进度: 295%
[====== ] 31.5% (2.72 GB/8.62 GB) | Speed: 49.0 MB/s | ETA: 2m 3s单线程进度: 315%
[====== ] 32.0% (2.76 GB/8.62 GB) | Speed: 41.3 MB/s | ETA: 2m 25s单线程进度: 320%
[====== ] 32.5% (2.80 GB/8.62 GB) | Speed: 33.7 MB/s | ETA: 2m 56s单线程进度: 325%
[======= ] 35.5% (3.06 GB/8.62 GB) | Speed: 8.4 MB/s | ETA: 11m 18s单线程进度: 355%
[======= ] 36.5% (3.15 GB/8.62 GB) | Speed: 44.5 MB/s | ETA: 2m 5s单线程进度: 365%
[======== ] 40.0% (3.45 GB/8.62 GB) | Speed: 54.5 MB/s | ETA: 1m 36s单线程进度: 400%
[========= ] 47.5% (4.10 GB/8.62 GB) | Speed: 20.3 MB/s | ETA: 3m 47s单线程进度: 475%
[========= ] 49.5% (4.27 GB/8.62 GB) | Speed: 59.1 MB/s | ETA: 1m 15s单线程进度: 495%
[========== ] 50.0% (4.31 GB/8.62 GB) | Speed: 37.0 MB/s | ETA: 1m 59s单线程进度: 500%
[========== ] 50.5% (4.35 GB/8.62 GB) | Speed: 38.7 MB/s | ETA: 1m 52s单线程进度: 505%
[============ ] 64.0% (5.51 GB/8.62 GB) | Speed: 5.1 MB/s | ETA: 10m 17s单线程进度: 640%
[============= ] 65.0% (5.60 GB/8.62 GB) | Speed: 24.0 MB/s | ETA: 2m 8s单线程进度: 650%
[============= ] 66.0% (5.69 GB/8.62 GB) | Speed: 44.1 MB/s | ETA: 1m 7s单线程进度: 660%
[============= ] 67.0% (5.78 GB/8.62 GB) | Speed: 103.6 MB/s | ETA: 28s单线程进度: 670%
[============= ] 69.5% (5.99 GB/8.62 GB) | Speed: 21.0 MB/s | ETA: 2m 7s单线程进度: 695%
[============== ] 73.5% (6.34 GB/8.62 GB) | Speed: 36.3 MB/s | ETA: 1m 4s单线程进度: 735%
[=============== ] 75.5% (6.51 GB/8.62 GB) | Speed: 79.4 MB/s | ETA: 27s单线程进度: 755%
[=============== ] 76.0% (6.55 GB/8.62 GB) | Speed: 24.3 MB/s | ETA: 1m 27s单线程进度: 760%
[=============== ] 76.5% (6.59 GB/8.62 GB) | Speed: 63.2 MB/s | ETA: 32s单线程进度: 765%
[=============== ] 77.0% (6.64 GB/8.62 GB) | Speed: 53.0 MB/s | ETA: 38s单线程进度: 770%
[=============== ] 77.5% (6.68 GB/8.62 GB) | Speed: 29.7 MB/s | ETA: 1m 6s单线程进度: 775%
[=============== ] 78.0% (6.72 GB/8.62 GB) | Speed: 79.5 MB/s | ETA: 24s单线程进度: 780%
[=============== ] 79.0% (6.81 GB/8.62 GB) | Speed: 5000.0 MB/s | ETA: 0s单线程进度: 790%
[=============== ] 79.5% (6.85 GB/8.62 GB) | Speed: 5000.0 MB/s | ETA: 0s单线程进度: 795%
[================ ] 80.0% (6.89 GB/8.62 GB) | Speed: 3.4 MB/s | ETA: 8m 41s单线程进度: 800%
[================ ] 80.5% (6.94 GB/8.62 GB) | Speed: 5000.0 MB/s | ETA: 0s单线程进度: 805%
[================ ] 81.0% (6.98 GB/8.62 GB) | Speed: 33.1 MB/s | ETA: 50s单线程进度: 810%
[================ ] 81.5% (7.02 GB/8.62 GB) | Speed: 53.7 MB/s | ETA: 30s单线程进度: 815%
[================ ] 83.0% (7.15 GB/8.62 GB) | Speed: 30.2 MB/s | ETA: 49s单线程进度: 830%
[================ ] 83.5% (7.20 GB/8.62 GB) | Speed: 68.1 MB/s | ETA: 21s单线程进度: 835%
[================ ] 84.0% (7.24 GB/8.62 GB) | Speed: 64.0 MB/s | ETA: 22s单线程进度: 840%
[================== ] 91.5% (7.88 GB/8.62 GB) | Speed: 10.2 MB/s | ETA: 1m 13s单线程进度: 915%
[=================== ] 95.0% (8.19 GB/8.62 GB) | Speed: 125.5 MB/s | ETA: 3s单线程进度: 950%
[=================== ] 96.0% (8.28 GB/8.62 GB) | Speed: 42.1 MB/s | ETA: 8s单线程进度: 960%
[=================== ] 96.5% (8.31 GB/8.62 GB) | Speed: 2.0 MB/s | ETA: 2m 34s单线程进度: 965%
[====================] 100.0% (8.62 GB/8.62 GB) | Speed: 0.8 MB/s | ETA: --单线程进度: 1000%
取消单线程进度的显示: | Speed: 0.8 MB/s | ETA: --单线程进度: 1000%
我只在乎整体的进度,同时根据处理的文件个数来换算输出的进度,每处理一个文件更新一次:package com.example;
import com.example.callback.ProgressCallback;
import java.io.*;
import java.nio.channels.FileChannel;
import java.util.concurrent.atomic.AtomicLong;
public class OptimizedFileCopier {
// 配置参数
private static final int BUFFER_SIZE = 64 * 1024; // 64KB缓冲
private static final long SMALL_FILE_THRESHOLD = 32 * 1024; // 32KB
private static final long MEDIUM_FILE_THRESHOLD = 128 * 1024 * 1024; // 128MB
private static final long PROGRESS_UPDATE_INTERVAL = 1024 * 1024; // 1MB更新一次进度
private static final long MIN_PROGRESS_UPDATE_INTERVAL_MS = 500; // 最小进度更新间隔(毫秒)
private static final double MAX_DISPLAY_SPEED = 5000; // 最大显示速度(MB/s)
// 速度平滑处理相关
private static final int SPEED_HISTORY_SIZE = 3;
private static final double[] speedHistory = new double[SPEED_HISTORY_SIZE];
private static int speedIndex = 0;
private static long lastSpeedCalcTime = 0;
private static long lastSpeedCalcBytes = 0;
public static boolean copyDirectoryWithProgress(File src, File dest, ProgressCallback callback)
throws IOException, InterruptedException {
// 参数校验
if (!src.isDirectory()) {
throw new IllegalArgumentException("Source path must be a directory");
}
if (!dest.exists() && !dest.mkdirs()) {
throw new IOException("Cannot create destination directory: " + dest.getAbsolutePath());
}
// 初始化状态
long totalSize = getDirectorySize(src);
System.out.println("Starting copy, total size: " + formatSize(totalSize));
AtomicLong copiedSize = new AtomicLong(0);
long startTime = System.currentTimeMillis();
long lastUpdateTime = startTime;
long lastUpdateSize = 0;
// 执行复制
copyDirectoryInternal(src, dest, totalSize, copiedSize, callback, startTime, lastUpdateSize, lastUpdateTime);
// 完成处理
if (callback != null) {
callback.onProgress(1000, 1000);
}
// 输出统计信息
long totalTime = System.currentTimeMillis() - startTime;
double avgSpeed = totalTime > 0 ? (totalSize / 1024.0 / 1024.0) / (totalTime / 1000.0) : 0;
System.out.printf("\nCopy completed! Total: %s, Time: %s, Avg speed: %.1f MB/s\n",
formatSize(totalSize), formatDuration(totalTime), avgSpeed);
return true;
}
private static void copyDirectoryInternal(File src, File dest, long totalSize,
AtomicLong copiedSize, ProgressCallback callback, long startTime,
long lastUpdateSize, long lastUpdateTime) throws IOException, InterruptedException {
File[] files = src.listFiles();
if (files == null) return;
for (File file : files) {
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException("Copy interrupted");
}
File target = new File(dest, file.getName());
if (file.isDirectory()) {
if (!target.exists() && !target.mkdirs()) {
throw new IOException("Cannot create subdirectory: " + target.getAbsolutePath());
}
copyDirectoryInternal(file, target, totalSize, copiedSize,
callback, startTime, lastUpdateSize, lastUpdateTime);
} else {
long copied = optimizedSmartCopy(file, target);
copiedSize.addAndGet(copied);
// 智能进度更新
long currentCopied = copiedSize.get();
long currentTime = System.currentTimeMillis();
if (currentCopied - lastUpdateSize >= PROGRESS_UPDATE_INTERVAL
|| currentCopied == totalSize
|| currentTime - lastUpdateTime >= MIN_PROGRESS_UPDATE_INTERVAL_MS) {
updateProgress(currentCopied, totalSize, callback, startTime);
lastUpdateSize = currentCopied;
lastUpdateTime = currentTime;
}
}
}
}
private static void updateProgress(long currentCopied, long totalSize,
ProgressCallback callback, long startTime) {
if (totalSize == 0) return;
// 计算进度 (0-1000)
int progress = (int) Math.min(1000, (currentCopied * 1000L) / Math.max(1, totalSize));
// 计算平滑后的速度
double speed = getSmoothedSpeed(startTime, currentCopied);
speed = Math.min(speed, MAX_DISPLAY_SPEED); // 限制最大显示速度
// 计算剩余时间
String remainingTime = calculateRemainingTime(startTime, currentCopied, totalSize, speed);
// 更新显示
System.out.printf("\r[%s] %.1f%% (%s/%s) | Speed: %.1f MB/s | ETA: %s",
buildProgressBar(progress),
progress / 10.0,
formatSize(currentCopied),
formatSize(totalSize),
speed,
remainingTime);
// 回调通知
if (callback != null) {
try {
callback.onProgress(progress, 1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
private static double getSmoothedSpeed(long startTime, long copiedBytes) {
long currentTime = System.currentTimeMillis();
long elapsed = currentTime - startTime;
if (elapsed < 100) return 0; // 初始阶段不计算速度
// 计算瞬时速度
double instantSpeed = (copiedBytes - lastSpeedCalcBytes) / 1024.0 / 1024.0 /
((currentTime - lastSpeedCalcTime) / 1000.0);
// 更新历史记录
speedHistory[speedIndex++ % SPEED_HISTORY_SIZE] = instantSpeed;
lastSpeedCalcTime = currentTime;
lastSpeedCalcBytes = copiedBytes;
// 计算平滑速度 (取最近3次的平均值)
double sum = 0;
int count = 0;
for (double s : speedHistory) {
if (s > 0) {
sum += s;
count++;
}
}
return count > 0 ? sum / count : 0;
}
private static String calculateRemainingTime(long startTime, long copiedBytes,
long totalBytes, double speed) {
if (copiedBytes <= 0 || copiedBytes >= totalBytes || speed <= 0.1) {
return "--";
}
double remainingMB = (totalBytes - copiedBytes) / (1024.0 * 1024.0);
int remainingSec = (int) (remainingMB / speed);
return formatDuration(remainingSec * 1000L);
}
private static String buildProgressBar(int progress) {
int completed = progress / 50;
completed = Math.min(20, completed); // 确保不超过20个字符
int remaining = 20 - completed;
return "=".repeat(completed) + " ".repeat(remaining);
}
private static String formatSize(long bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
if (bytes < 1024 * 1024 * 1024) return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
return String.format("%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0));
}
private static String formatDuration(long millis) {
long seconds = millis / 1000;
if (seconds < 60) return seconds + "s";
if (seconds < 3600) return String.format("%dm %ds", seconds/60, seconds%60);
return String.format("%dh %dm", seconds/3600, (seconds%3600)/60);
}
private static long optimizedSmartCopy(File src, File dest) throws IOException {
long size = src.length();
if (size < SMALL_FILE_THRESHOLD) {
return smallFileCopy(src, dest);
} else if (size < MEDIUM_FILE_THRESHOLD) {
return mediumFileCopy(src, dest);
} else {
return largeFileCopy(src, dest);
}
}
private static long smallFileCopy(File src, File dest) throws IOException {
try (FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dest)) {
byte[] buffer = new byte[BUFFER_SIZE];
long total = 0;
int n;
while ((n = in.read(buffer)) != -1) {
out.write(buffer, 0, n);
total += n;
}
return total;
}
}
private static long mediumFileCopy(File src, File dest) throws IOException {
try (FileChannel inChannel = new FileInputStream(src).getChannel();
FileChannel outChannel = new FileOutputStream(dest).getChannel()) {
return inChannel.transferTo(0, inChannel.size(), outChannel);
}
}
private static long largeFileCopy(File src, File dest) throws IOException {
try (FileChannel inChannel = new FileInputStream(src).getChannel();
FileChannel outChannel = new FileOutputStream(dest).getChannel()) {
long size = inChannel.size();
long position = 0;
long chunkSize = 16 * 1024 * 1024; // 16MB分块
while (position < size) {
long remaining = size - position;
long transferSize = Math.min(chunkSize, remaining);
position += inChannel.transferTo(position, transferSize, outChannel);
}
return size;
}
}
public static long getDirectorySize(File dir) {
if (dir == null || !dir.exists() || !dir.isDirectory()) {
return 0;
}
long size = 0;
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
size += getDirectorySize(file);
} else {
size += file.length();
}
}
}
return size;
}
}