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

递归复制文件时SwingWorker进度条不更新问题求助

SwingWorker递归文件复制进度条不实时更新的解决方法

核心问题分析

  1. EDT被阻塞:按钮点击事件中的while (!worker.isDone())循环运行在事件调度线程(EDT)中,完全占用UI线程,导致所有UI更新操作被挂起,直到复制完成才会一次性刷新。
  2. 后台线程直接操作UI:copyFiles方法里直接调用label.setText修改UI组件,违反Swing单线程模型——所有UI操作必须在EDT执行,否则会导致UI异常或不更新。
  3. 进度发布时机错误:仅在所有文件复制完成后调用一次publish(copiedFiles),没有在每次复制单个文件后发布进度,process方法无法实时接收进度数据。
  4. 线程安全隐患:totalFiles和copiedFiles作为类级共享变量,多线程访问时没有同步机制,可能导致计数不准确。

修复方案及代码实现

针对上述问题,修正后的代码如下:

package swingworkertest;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JProgressBar;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.StandardCopyOption;
import java.util.List;

import javax.swing.BoxLayout;
import javax.swing.SwingWorker;

public class App extends JFrame {
    private JPanel mainPanel;
    private JButton button;
    private JLabel label;
    private JProgressBar progress;
    
    private int totalFiles = 0;
    
    // 递归统计文件总数
    private void getTotalFiles(File src) {
        File[] files = src.listFiles();
        if (files == null) return;
        for (File file : files) {
            if (file.isDirectory()) {
                getTotalFiles(file);
            } else {
                totalFiles++;
            }
        }
    }
    
    // 递归复制文件,每次复制后通知进度
    private void copyFiles(File src, File dst, SwingWorker<?, Integer> worker) throws IOException {
        File[] files = src.listFiles();
        if (files == null) return;
        
        for (File file : files) {
            File target = new File(dst, file.getName());
            if (file.isDirectory()) {
                if (!target.exists()) {
                    target.mkdirs();
                }
                copyFiles(file, target, worker);
            } else {
                Files.copy(file.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
                // 每次复制完成后发布当前已复制数量
                worker.publish(1);
            }
        }
    }
    
    public App(String title) {
        this.setTitle(title);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        String srcPath = "C:\\Users\\Vinny\\Desktop\\workingDirectory";
        String dstPath = "C:\\Users\\Vinny\\Desktop\\copyToHere";
        File src = new File(srcPath);
        File dst = new File(dstPath);
        
        mainPanel = new JPanel();
        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
        
        button = new JButton("Copy");
        label = new JLabel("Files Copied: 0/0");
        progress = new JProgressBar();
        progress.setStringPainted(true);
        
        button.addActionListener((e) -> {
            button.setEnabled(false); // 防止重复点击
            totalFiles = 0;
            getTotalFiles(src);
            label.setText("Files Copied: 0/" + totalFiles);
            progress.setMaximum(totalFiles);
            
            SwingWorker<Boolean, Integer> worker = new SwingWorker<>() {
                private int copiedFiles = 0;
                
                @Override
                protected Boolean doInBackground() throws Exception {
                    copyFiles(src, dst, this);
                    return true;
                }
                
                @Override
                protected void process(List<Integer> chunks) {
                    // 累加每次发布的进度增量
                    copiedFiles += chunks.size();
                    // 更新标签和进度条(process方法运行在EDT)
                    label.setText("Files Copied: " + copiedFiles + "/" + totalFiles);
                    progress.setValue(copiedFiles);
                }
                
                @Override
                protected void done() {
                    button.setEnabled(true);
                    System.out.println("Files Copied");
                }
            };
            worker.execute();
        });
        
        mainPanel.add(button);
        mainPanel.add(label);
        mainPanel.add(progress);
        
        this.add(mainPanel);
        this.pack();
        this.setVisible(true);
    }

    public static void main(String[] args) {
        // 在EDT启动应用
        javax.swing.SwingUtilities.invokeLater(() -> new App("File Copy"));
    }
}

关键修复点说明

  • 移除阻塞EDT的循环:删除按钮事件中的死循环,让EDT能够正常处理UI刷新请求。
  • 通过publish/process更新UI:每次复制单个文件后调用worker.publish(1),在process方法中累加进度并更新UI,确保所有UI操作在EDT执行。
  • 避免共享变量冲突:将copiedFiles改为SwingWorker内部的变量,避免多线程共享带来的线程安全问题。
  • 优化文件操作:使用File.listFiles()减少重复创建File对象,添加StandardCopyOption.REPLACE_EXISTING处理文件覆盖场景,同时添加按钮禁用逻辑防止重复触发复制。

内容的提问来源于stack exchange,提问作者Vince

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 14:59:52