使用 Java 复制文件时的进度条
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20767241/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Progress bar while copying files with Java
提问by DerStrom8
I'm sure this question has been asked before, but none of the answers I found will work very well with my existing code. I'm posting this question in case there's a way to do it without completely redoing what I have so far.
我确定这个问题之前已经被问过,但是我找到的答案都不能很好地与我现有的代码一起使用。我正在发布这个问题,以防万一有一种方法可以在不完全重做我目前所做的事情的情况下做到这一点。
The idea is to display a very basic progress bar while copying files and directories from one drive to another. I have a class called BasicCopy that is designed to copy the contents of the Pictures, Documents, videos, and Music folders (standard on Windows machines) to folders of the same names within a backup directory on a second drive. Here is the class so far:
这个想法是在将文件和目录从一个驱动器复制到另一个驱动器时显示一个非常基本的进度条。我有一个名为 BasicCopy 的类,旨在将图片、文档、视频和音乐文件夹(Windows 机器上的标准)的内容复制到第二个驱动器上备份目录中的同名文件夹中。这是迄今为止的课程:
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.io.FileUtils;
public class BasicCopy {
String username = "";
String mainDrive = "";
String backupDrive = "";
String backupDir = "";
String[] directories;
public BasicCopy(String inDrive, String outDrive, String username){
mainDrive = inDrive;
backupDrive = outDrive;
this.username = username;
createBackupDirectory();
copyDirectories();
close();
}
//Create backup directory
public void createBackupDirectory(){
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy_HMMSS");
String timestamp = sdf.format(date);
backupDir = backupDrive + ":\" + "Backup " + timestamp;
File backupDirectory = new File(backupDir);
backupDirectory.mkdir();
}
public void copyDirectories(){
//Main directories
String pics = mainDrive + ":\Users\" + username + "\Pictures";
String docs = mainDrive + ":\Users\" + username + "\Documents";
String vids = mainDrive + ":\Users\" + username + "\Videos";
String musc = mainDrive + ":\Users\" + username + "\Music";
//Backup directories
String bkPics = backupDir + "\Pictures";
String bkDocs = backupDir + "\Documents";
String bkVids = backupDir + "\Documents";
String bkMusc = backupDir + "\Pictures";
String[] directories = {pics, docs, vids, musc};
String[] bkDirectories = {bkPics, bkDocs, bkVids, bkMusc};
//Loop through directories and copy files
for (int i = 0; i < directories.length; i++){
File src = new File(directories[i]);
File dest = new File(bkDirectories[i]);
try{
FileUtils.copyDirectory(src, dest);
} catch (IOException e){
e.printStackTrace();
}
}
}
/* Close current process */
public void close(){
System.exit(0);
}
}
I have a method in a previous class that measures the total size of the directories, so I could pass that in to this class if necessary. However, I currently loop through only the four directories, so I expect I wouldn't be able to increment a progress bar with any higher resolution than 25% per tick. I am wondering if there's a way I might change it so that I can include a progress bar to monitor it, and have it be a little more accurate? Furthermore, I'm not sure if this should be asked in a different thread or not, but this method of file copying takes a very long time. It takes hours to copy 500MB worth of files, and I was wondering if there might be a way to speed it up? That part isn't a priority though. Right now I'm mainly interested in adding in a progress bar. Cheers!
我在以前的类中有一个方法可以测量目录的总大小,因此我可以在必要时将其传递给这个类。但是,我目前只循环遍历四个目录,所以我希望我无法增加分辨率高于每个刻度 25% 的进度条。我想知道是否有一种方法可以更改它,以便我可以包含一个进度条来监视它,并且让它更准确一些?此外,我不确定是否应该在不同的线程中询问这个问题,但是这种文件复制方法需要很长时间。复制价值 500MB 的文件需要几个小时,我想知道是否有办法加快速度?不过,那部分不是优先事项。现在我主要对添加进度条感兴趣。干杯!
EDIT:
编辑:
After some fiddling I realized I could probably use code similar to this (This exact code may or may not work--I just jotted it down quickly so I wouldn't forget it, it is still untested). This would allow me to update the progress bar for each file copied.
经过一番摆弄之后,我意识到我可能可以使用与此类似的代码(这个确切的代码可能有效也可能无效——我只是快速记下它以免忘记它,它仍未经过测试)。这将允许我为复制的每个文件更新进度条。
for (int i = 0; i < directories.length; i++){
File dir = new File(directories[i]);
File dest = new File(bkDirectories[i]);
for(File file: dir.listFiles()){
try{
FileUtils.copyDirectory(file, dest);
//update progress bar here
} catch (IOException e){
e.printStackTrace();
}
}
}
EDIT #2:
编辑#2:
I have worked a bit more on the code and I believe I've figured most of it out. The question now is about a SwingWorker, which I believe I'll need in order to run the long-term methods in the background. Otherwise the GUI becomes unresponsive (lots of documentation on this in the Java docs). However, this is where I get stuck. I've only used a SwingWorker once before, and that was mainly with copied code. I am wondering how I could implement that using the following code so that the progress bar (and the rest of the frame) actually appears.
我在代码上做了更多的工作,我相信我已经弄清楚了大部分。现在的问题是关于 SwingWorker,我相信我需要它才能在后台运行长期方法。否则 GUI 变得无响应(Java 文档中有很多关于此的文档)。然而,这就是我陷入困境的地方。我以前只使用过一次 SwingWorker,那主要是复制代码。我想知道如何使用以下代码实现它,以便实际出现进度条(和框架的其余部分)。
Updated code:
更新代码:
import java.awt.Toolkit;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JTextArea;
import javax.swing.JButton;
import javax.swing.JProgressBar;
import javax.swing.JLabel;
import org.apache.commons.io.FileUtils;
@SuppressWarnings("serial")
public class BasicCopy extends JFrame {
private JPanel contentPane;
private JTextArea txtCopiedDirs;
private JButton btnCancel;
private JProgressBar progressBar;
private JLabel lblCopying;
private String mainDrive;
private String backupDrive;
private String username;
private String backupDir;
long totalSize = 0; //total size of directories/files
long currentSize = 0; //current size of files counting up to ONE_PERCENT
int currentPercent = 0; //current progress in %
long ONE_PERCENT; //totalSize / 100
public BasicCopy() {
}
public BasicCopy(String inDrive, String outDrive, String username, long space){
mainDrive = inDrive;
backupDrive = outDrive;
this.username = username;
totalSize = space;
ONE_PERCENT = totalSize/100;
createGUI();
/* Should not have these here!
* Pretty sure I need a SwingWorker
*/
createBackupDirectory();
copyDirectories();
}
public void createGUI(){
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("Backup Progress");
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
txtCopiedDirs = new JTextArea();
txtCopiedDirs.setBounds(10, 56, 414, 125);
contentPane.add(txtCopiedDirs);
btnCancel = new JButton("Cancel");
btnCancel.setBounds(169, 227, 89, 23);
contentPane.add(btnCancel);
progressBar = new JProgressBar(0, 100);
progressBar.setBounds(10, 192, 414, 24);
progressBar.setValue(0);
contentPane.add(progressBar);
lblCopying = new JLabel("Now backing up your files....");
lblCopying.setBounds(10, 11, 157, 34);
contentPane.add(lblCopying);
setVisible(true);
}
//Create backup directory
public void createBackupDirectory(){
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy_HMMSS");
String timestamp = sdf.format(date);
backupDir = backupDrive + ":\" + "Backup " + timestamp;
File backupDirectory = new File(backupDir);
backupDirectory.mkdir();
}
public void copyDirectories(){
//Main directories
String pics = mainDrive + ":\Users\" + username + "\Pictures";
String docs = mainDrive + ":\Users\" + username + "\Documents";
String vids = mainDrive + ":\Users\" + username + "\Videos";
String musc = mainDrive + ":\Users\" + username + "\Music";
//Backup directories
String bkPics = backupDir + "\Pictures";
String bkDocs = backupDir + "\Documents";
String bkVids = backupDir + "\Documents";
String bkMusc = backupDir + "\Pictures";
String[] directories = {pics, docs, vids, musc};
String[] bkDirectories = {bkPics, bkDocs, bkVids, bkMusc};
//Loop through directories and copy files
for (int i = 0; i < directories.length; i++){
File dir = new File(directories[i]);
File dest = new File(bkDirectories[i]);
for(File file: dir.listFiles()){
try{
FileUtils.copyDirectory(file, dest);
if(getDirSize(file) >= ONE_PERCENT){
currentPercent++;
progressBar.setValue(currentPercent);
currentSize = 0;
} else {
currentSize = currentSize + getDirSize(file);
if(currentSize >= ONE_PERCENT){
currentPercent++;
progressBar.setValue(currentPercent);
currentSize = 0;
}
}
} catch (IOException e){
e.printStackTrace();
}
}
}
}
public static Long getDirSize(File directory) {
long size = 0L;
if (directory.listFiles() != null){
for (File file : directory.listFiles()) {
size += file.isDirectory() ? getDirSize(file) : file.length();
}
}
return size;
}
/* Close current window */
public void closeWindow() {
WindowEvent close = new WindowEvent(this, WindowEvent.WINDOW_CLOSING);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(close);
System.exit(0);
}
}
采纳答案by DerStrom8
I have found a solution that works. It still has some minor glitches, but mainly with unimportant details. Here is what I have now, and it seems to be working.
我找到了一个有效的解决方案。它仍然有一些小故障,但主要是不重要的细节。这是我现在所拥有的,它似乎正在起作用。
class Task extends SwingWorker<Void, Void>{
@Override
public Void doInBackground(){
setProgress(0);
//Create Backup Directory
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy_HMMSS");
String timestamp = sdf.format(date);
backupDir = backupDrive + ":\" + "Backup_" + timestamp;
File backupDirectory = new File(backupDir);
backupDirectory.mkdir();
//Copy Files
//Main directories
String pics = mainDrive + ":\Users\" + username + "\Pictures\";
String docs = mainDrive + ":\Users\" + username + "\Documents\";
String vids = mainDrive + ":\Users\" + username + "\Videos\";
String musc = mainDrive + ":\Users\" + username + "\Music\";
//Backup directories
String bkPics = backupDir + "\Pictures\";
String bkDocs = backupDir + "\Documents\";
String bkVids = backupDir + "\Documents\";
String bkMusc = backupDir + "\Pictures\";
String[] directories = {pics, docs, vids, musc};
String[] bkDirectories = {bkPics, bkDocs, bkVids, bkMusc};
//Loop through directories and copy files
for (int i = 0; i < directories.length; i++){
File dir = new File(directories[i]);
File dest = new File(bkDirectories[i]);
for(File file: dir.listFiles()){
try{
if(file.isFile()){
FileUtils.copyFileToDirectory(file, dest);
txtCopiedDirs.append(file.getAbsolutePath() + "\n");
} else if (file.isDirectory()){
FileUtils.copyDirectoryToDirectory(file, dest);
txtCopiedDirs.append(file.getAbsolutePath() + "\n");
}
if(getDirSize(file) >= ONE_PERCENT){
currentPercent = getDirSize(file)/ONE_PERCENT;
progressBar.setValue((int)currentPercent);
currentSize = 0;
} else {
currentSize = currentSize + getDirSize(file);
if(currentSize >= ONE_PERCENT){
currentPercent = currentSize/ONE_PERCENT;
currentPercent++;
progressBar.setValue((int)currentPercent);
currentSize = 0;
}
}
} catch (IOException e){
e.printStackTrace();
}
}
}
return null;
}
@Override
public void done(){
closeWindow();
}
}
This class is contained within the main class, and is started using the following code:
此类包含在主类中,并使用以下代码启动:
Task task = new Task();
task.execute();
This is called immediately after the frame is created and set visible.
在创建框架并将其设置为可见后立即调用此方法。
Now, as I mentioned this still isn't perfect. For example, the check to see if it's a file or a directory before copying it should be replaced with a recursive function, so that it loops through each individual file, rather than just copy the directory if it's a full directory. This will allow for more accurate updating of the progress bar and will show the individual files being copied in the text area, rather than files and directories.
现在,正如我所提到的,这仍然不完美。例如,在复制之前检查它是文件还是目录应该用递归函数替换,以便它循环遍历每个单独的文件,而不是仅复制目录(如果它是完整目录)。这将允许更准确地更新进度条,并将在文本区域中显示正在复制的单个文件,而不是文件和目录。
Anyway, I just wanted to post this code to show what has worked for me, without completely redoing what I had. I will keep working on it though, and post any important updates that may help future readers.
无论如何,我只是想发布这段代码来展示对我有用的东西,而不是完全重做我拥有的东西。不过,我会继续努力,并发布任何可能对未来读者有所帮助的重要更新。
Thanks everyone for the responses, they've helped a lot!
谢谢大家的回复,帮了大忙!
回答by Amir Afghani
I'm assuming you want a graphical progress bar, and not a console based (tui
) solution. Have you read this Swing tutorial?
我假设您想要一个图形进度条,而不是基于控制台的 ( tui
) 解决方案。你读过这个 Swing教程吗?
EDIT: If you want one tick of your progress bar per file, you simply need to tell the progress bar constructor how many total ticks there are, something like:
编辑:如果您希望每个文件的进度条有一个滴答声,您只需要告诉进度条构造函数总共有多少滴答声,例如:
progressBar = new JProgressBar(0, allFilesInAllDirectoriesLength);
and organize your for loop to work on each file, instead of looping on the directories.
并组织您的 for 循环来处理每个文件,而不是在目录上循环。
回答by Tomas Bisciak
This might be more complicated and maybe stupid idea bud,Maybe it helps so i decided to post it. Use your own method to copy file ,check file size ,take 1 percent from it and each time you reach amount of bites/mb/ that represent 1 percent of that file update your progress bar
这可能更复杂,也许是愚蠢的想法,也许它有帮助,所以我决定发布它。使用您自己的方法复制文件,检查文件大小,从中取出 1%,每次达到代表该文件 1% 的字节数/mb/ 时,更新您的进度条
Im short on time so i will at least paste some code with comments so you get idea of what i think.
我时间不够,所以我至少会粘贴一些带有注释的代码,以便您了解我的想法。
//check size of files/dir to copy,before calling method bellow
//then you coud use for loop to do all the work
//make counter of how many mb/bits you already did.
public void copyDirectory(File sourceLocation , File targetLocation) throws IOException {
if (sourceLocation.isDirectory()) {
if (!targetLocation.exists()) {
targetLocation.mkdir();
}
String[] children = sourceLocation.list();
for (int i=0; i<children.length; i++) {
copyDirectory(new File(sourceLocation, children[i]),
new File(targetLocation, children[i]));
}
} else {
InputStream in = new FileInputStream(sourceLocation);
OutputStream out = new FileOutputStream(targetLocation);
// Copy the bits from instream to outstream
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
//increment counter
//check if counter is above next 1% of size of your dirs
//if you are then update progress bar by one percent
}
in.close();
out.close();
}
}
This solution is not tested bud this is how i woud start to aproach this problem.
这个解决方案没有经过测试,这就是我开始解决这个问题的方式。
回答by Damiano
Here is my idea using Java 8 and apache FileUtils(can be replaced by any other). It simply checks (in separate thread) directory size every nseconds:
这是我使用 Java 8 和apache FileUtils 的想法(可以替换为任何其他)。它只是每n秒检查一次(在单独的线程中)目录大小:
FUNCTIONAL INTERFACE:
功能界面:
public interface DirectoryMovementStatusFeedback {
void notifyStatus(double percentMoved, double speedInMB);
}
SERVICE:
服务:
public class FileMovementStatusUpdater {
private long checkInterval = 2000;
private boolean interrupted = false;
public long getCheckInterval() {
return checkInterval;
}
public void setCheckInterval(long checkInterval) {
this.checkInterval = checkInterval;
}
public void monitor(File directory, long desiredSize, DirectoryMovementStatusFeedback feedback) {
new Thread(new Runnable() {
@Override
public void run() {
try {
double percentageMoved = 0;
double lastCheckedSize = 0;
do {
double currentSize = directory.exists() ? FileUtils.sizeOfDirectory(directory) : 0;
double speed = (currentSize - lastCheckedSize) / (1024 * checkInterval);
lastCheckedSize = currentSize;
percentageMoved = 100 * currentSize / desiredSize;
feedback.notifyStatus(percentageMoved, speed);
Thread.sleep(checkInterval);
} while (percentageMoved < 100 && !interrupted);
} catch (Exception e) {
System.err.println("Directory monitor failed. " + e.getMessage());
}
}
}).start();
}
public void stopMonitoring() {
this.interrupted = true;
}
USAGE:
用法:
FileMovementStatusUpdater dirStatus = new FileMovementStatusUpdater();
try {
dirStatus.monitor(destination, FileUtils.sizeOfDirectory(source), (percent, speed) -> {
progressBar.setValue(percent);
speedLabel.setValue(speed+" MB/s");
});
FileUtils.moveDirectory(source, destination);
} catch (Exception e) {
// something
}
dirStatus.stopMonitoring();