java 进度条不能刷新解决方案 (已更新一次)

java期末考试都考完了,实验课却还没完,课程设计也还没开始做,本来打算把最后两次实验尽快做完,然后好好做课设,(我早就想写的坦克大战,过几天写好了,也肯定要整理发布出来)。可却被多线程一题给坑了,题目如下:

    编写GUI程序,实现文件的复制功能,要求以进度条实时显示复制进度

复制都很简单,关键进度条实时显示进度,昨晚调到1:40,今天早上7点起来一直调到11:00,才勉强解决,还有一点问题,过几天等老师公布答案再来完善,老师的方案应该比较完善

我复制过程中调用System.out.println(progressBar.getValue());控制台都能实时显示当前进度从0~100,但UI界面的进度条就是雷打不动,一直0%懒得我都想踢死它了。。直到复制完成后才突然突变为100%,明显这不是实时显示


百度好几个小时,最终发现“当应用程序在事件线程中执行长时间的操作时,会阻塞正常的AWT事件处理,因此阻止了重绘操作的发生。”也即API本身就是线程不安全的。因为开始我的代码是在run方法内直接写:

    progressBar.setValue(jd);

这个操作一直被阻塞了,UI界面的进度条也就实时刷新了

后来改成:

Dimension d = progressBar.getSize();
Rectangle rect = new Rectangle(0, 0, d.width, d.height);
progressBar.setValue(jd);

progressBar.paintImmediately(rect);

扫描二维码关注公众号,回复: 2111437 查看本文章

才初步解决:




但是现在还有一个问题,就是run方法要持续输出一段文本到控制台(System.out.println("我没有结束"))(也即不是空死循环),不然它就始终抢占不到CPU,没机会执行,CPU一直被复制的主进程抢占,甚至让复制的进制sleep也不会切到run里,可能我的代码还不完善,过几天再来解决吧。

闲话少说,上最终代码:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

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

public class Exp10_3 extends JFrame implements Runnable {

	boolean b = false;// 线程执行的标志

	int jd = 0;// 当前进度
	long sum = 0;// 当前共复制的长度

	JButton button1 = new JButton("被复制");
	JTextField beCyFile = new JTextField(30);
	JButton button2 = new JButton("复制到");
	JTextField CyToDir = new JTextField(30);
	JButton Start = new JButton("开始复制");
	JLabel label = new JLabel("进度");
	JProgressBar progressBar = new JProgressBar();

	void initUI() {

		JPanel top1 = new JPanel();
		JPanel top2 = new JPanel();
		JPanel end = new JPanel();
		top1.add(button1);
		top1.add(beCyFile);
		top2.add(button2);
		top2.add(CyToDir);
		setLayout(new GridLayout(4, 1));
		add(top1);
		add(top2);
		add(Start);
		progressBar.setStringPainted(true);// 设置进度条上字符串可显示
		progressBar.setBackground(Color.GREEN);// 设置进度条颜色
		end.add(label);
		end.add(progressBar);
		add(end);

		button1.addActionListener(new ActionListener() {
			// 将选择文件的绝对路径显示到被复制后的文本框内
			@Override
			public void actionPerformed(ActionEvent e) {
				JFileChooser fc = new JFileChooser();
				fc.setFileHidingEnabled(false);// 显示隐藏文件
				fc.setMultiSelectionEnabled(false);// 允许多选
				fc.setDialogTitle("请选择要复制的文件");
				if (fc.showOpenDialog(Exp10_3.this) == JFileChooser.APPROVE_OPTION) {
					beCyFile.setText(fc.getSelectedFile().getAbsolutePath());
					CyToDir.setText(fc.getSelectedFile().getParent());// 获取file文件的父目录(强大的API) 自我设定:默认复制到同一目录
				}
			}
		});

		button2.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				JFileChooser fc = new JFileChooser();
				fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);// 仅仅能选择目录
				fc.setDialogTitle("请选择要复制到的路径");
				if (fc.showOpenDialog(Exp10_3.this) == JFileChooser.APPROVE_OPTION) {
					CyToDir.setText(fc.getSelectedFile().getAbsolutePath());
				}
			}
		});


		Start.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				jd = 0;
				b = true;
				try {
					String file1Path = beCyFile.getText();
					File file1 = new File(beCyFile.getText());// 被复制的文件
					String file2Path = CyToDir.getText() + "\\copy" + file1.getName();// 复制完后新文件路径名
					File file2 = new File(file2Path);// 新建复制文件

					BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file1Path));
					BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file2Path));

					byte[] be = new byte[1024 * 1024];// 之前定义为b和boolean重复了 屏蔽了全局标志b
					int len = bis.read(be);
					long sum = 0;
					long file1len = file1.length();
					while (-1 != len) {
						bos.write(be, 0, len);// 一次读一个字节数组 换行也会读 不用自动换行了
						bos.flush();
						sum += len;
						jd = (int) (sum * 1.0 / file1len * 100);// 之前没有乘1.0 且多写了一个(int) 导致jd一直是0 最后一次突变100
						len = bis.read(be);
					}
					//// 最后再绘一次
					Dimension d = progressBar.getSize();
					Rectangle rect = new Rectangle(0, 0, d.width, d.height);
					progressBar.setValue(jd);
					progressBar.paintImmediately(rect);
					b = false;
					System.out.println("b=" + b);
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		});

		Thread t = new Thread(this);
		t.start();
		pack();
		setLocationRelativeTo(null);
		setVisible(true);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	public static void main(String[] args) {
		Exp10_3 t = new Exp10_3();
		t.initUI();
	}

	@Override
	public void run() {
		while (true) {

			if (b) {

                               //progressBar.setValue(jd);//之前run内就这一行,进度条一直不刷新

				Dimension d = progressBar.getSize();
				Rectangle rect = new Rectangle(0, 0, d.width, d.height);
				progressBar.setValue(jd);
				progressBar.paintImmediately(rect);
				if (jd == 100) {
					b = false;
//					System.out.println("run内b=" + b);// 不能写return 此进程不能结束 一直开着
				}
			}
			//System.out.println("我没有结束");//删了此行进度条就又不刷新了	

                        //第一次改进  上面一行换成下面5行  即输出操作改成停顿1ms

                        try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();

			}

		}
	}
}



写报告时发现有了一个更好的改进方法,可以说基本完全解决了这个问题了:

将run方法内的:

System.out.println("我没有结束");//删了此行进度条就又不刷新了

改成:

			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}


就行了。

让刷新进度的进程有停顿操作就行了  神奇 有停顿操作反而容易抢到CPU,自己还没想到什么好的解释(这几天写坦克大战时又遇到了类似问题,就是暂停与继续操作。必须在sleep之后判断,也即是while(true){}循环体为空的话,cpu似乎将该线程视为垃圾线程,不再执行了。。),不过问题确乎解决了(而且拿图灵祖师爷的模仿游戏电影1.73G试验了下,复制的还挺快的)。大神看了能且知道作何解释的望告知。(代码段已更新)


猜你喜欢

转载自blog.csdn.net/hza419763578/article/details/80690689