问题
I want to keep my user informed of the progress of an I/O operation. At the moment I've got an inner class that I kick off before I start my I/O and stop after it's done. It looks like this:
class ProgressUpdater implements Runnable {
private Thread thread;
private long last = 0;
private boolean update = true;
private long size;
public ProgressUpdater(long size) {
this.size = size;
thread = new Thread(this);
}
@Override
public void run() {
while (update) {
if (position > last) {
last = position;
double progress = (double) position / (double) size * 100d;
parent.setProgress((int) progress);
}
}
}
public void start() {
thread.start();
}
public void stop() {
update = false;
parent.setProgress(100);
}
}
parent is my reference to my UI and position is a field in my outer class that represents how far in the I/O we have progressed. I set progress to 100% when stopping because sometimes the I/O finishes and stops my updater before it can finish updating the previous increment. That just ensures it's at 100%.
At the moment, this works, and I use it like this:
ProgressUpdater updater = new ProgressUpdater(file.length());
updater.start();
//do I/O
//...
updater.stop();
The problem is that the loop eats CPU pretty badly. I tried throwing a lock (with a wait/notify) in there but I don't know what I'm doing when it comes to using wait/notify so it just hung my thread. What can I do to stop it from using so many CPU cycles?
回答1:
You should look into using a SwingWorker. Basically you do all the IO on the background thread that the SwingWorker provides, and there are built-in ways of reporting the progress to the swing thread.
Then whenever you update that position, you can automatically update the progress, rather than polling continuously, which is what you've done so far.
Take a look at Swingworker Timeout or SwingWorker with FileReader to see if either will help.
An alternative solution, if you don't want to use a SwingWorker, would probably just be to, instead of updating that position value, update the progress bar directly, with a call like this:
SwingUtilities.invokeLater(new Runnable() {
@Override public void run() {
getMyProgressBar().setValue(position);
}
});
Assuming you've set up the progress bar so that its max is the size of the file.
回答2:
First of all never use Thread to update GUI (Swing components) in java. Instead use javax.swing.Timer or javax.swing.SwingWorker. And for Basic I/O operation use
ProgressMonitorInputStream .
As simple example of reading a file and displaying it in JTextArea and with progrressbar..Have a look at the code given below:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.io.*;
import java.beans.*;
class ProgressBarFrame extends JFrame
{
JProgressBar progressBar;
int BUFFERSIZE = 10;
JTextArea textArea;
MyWorker worker;
private void createAndShowGUI()
{
progressBar = new JProgressBar(0,100);
progressBar.setStringPainted(true);
JButton button = new JButton("Read File");
textArea = new JTextArea(30,100);
JScrollPane jsp = new JScrollPane(textArea);
Container c = getContentPane();
c.add(jsp);
c.add(progressBar,BorderLayout.NORTH);
c.add(button,BorderLayout.SOUTH);
worker = new MyWorker();
button.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent evt)
{
textArea.setText("");
progressBar.setValue(0);
if (worker.getState()== SwingWorker.StateValue.DONE || worker.getState()==SwingWorker.StateValue.STARTED)
{
worker.cancel(true);
worker = new MyWorker();
}
worker.execute();
}
});
pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
class MyWorker extends SwingWorker<Void, String>
{
MyWorker()
{
addPropertyChangeListener(new PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent evt)
{
if ("progress".equals(evt.getPropertyName()))
{
progressBar.setValue((Integer)evt.getNewValue());
}
}
});
}
@Override
public Void doInBackground() throws IOException
{
File file = new File("ProgressBarFrame.java");
long size = file.length();
long temp = 0;
BufferedInputStream bfin = new BufferedInputStream(new FileInputStream(file));
byte[] buffer=new byte[BUFFERSIZE];
int totalRead = -1;
while ((totalRead=bfin.read(buffer))!=-1 && ! isCancelled())
{
temp = temp + totalRead;
publish(new String(buffer));
if (bfin.available()<BUFFERSIZE && bfin.available()!= 0)
buffer = new byte[bfin.available()];
else if(bfin.available()==0)
buffer = new byte[1];
else
buffer=new byte[BUFFERSIZE];
setProgress((int)((temp/(float)size) * 100));
try{Thread.sleep(1);}catch(Exception ex){}
}
setProgress(100);
return null;
}
@Override
protected void process(List<String> chunks)
{
for (String value : chunks)
{
textArea.append(value);
}
}
}
public static void main(String st[])
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
ProgressBarFrame pf = new ProgressBarFrame();
pf.createAndShowGUI();
}
});
}
}
来源:https://stackoverflow.com/questions/15710814/progress-bar-updater-using-up-cpu