长任务
- 需要较长的时间完成
- 需要显示进度
- 需要显示结果
- 工作经常会有一些耗时间的操作,例如上传、下载、编码、解码 …
长任务练习
- 拷贝文件时,当文件较大时(几个G)应显示进度.
- 长任务执行时,应该注意释放CPU
- 不要过于频繁的更新显示,一般0.5~1秒更新一次
示例代码
package my;
import java.awt.Container;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class Swing2
{
private static void createGUI()
{
// JFrame指一个窗口,构造方法的参数为窗口标题
// 语法:因为MyFrame是JFrame的子类,所以可以这么写
JFrame frame = new MyFrame("Swing Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置窗口的其他参数,如窗口大小
frame.setSize(500, 300);
// 显示窗口
frame.setVisible(true);
}
public static void main(String[] args)
{
// 此段代码间接地调用了 createGUI(),具体原理在 Swing高级篇 里讲解
// 初学者先照抄此代码框架即可
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run()
{
createGUI();
}
});
}
}
核心类MyFrame.java
package my;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
public class MyFrame extends JFrame
{
JButton okButton = new JButton("开始");
WaitDialog waitDialog = null;
public MyFrame(String title)
{
super(title);
// Content Pane
JPanel root = new JPanel();
this.setContentPane(root);
root.setLayout(new FlowLayout());
root.add(okButton);
okButton.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e)
{
onMouseClicked();
}
});
}
// 按钮点击事件处理
private void onMouseClicked()
{
// 创建任务
// 注意:在Win10下不允许直接在根目录下创建文件,所以建一个子目录来测试
File srcFile = new File("c:/example/a1.rar");
File dstFile = new File("c:/example/a2.rar");
CopyTask th = new CopyTask();
th.execute(srcFile, dstFile);
// 提示等待对话框
waitDialog = new WaitDialog(this);
waitDialog.exec();
}
private class CopyTask extends Thread
{
private File srcFile, dstFile;
// 传入参数,并启动线程
public void execute(File srcFile, File dstFile)
{
this.srcFile = srcFile;
this.dstFile = dstFile;
start(); // 启动线程
}
@Override
public void run()
{
// 处理任务
try {
doInBackground();
}catch(Exception e)
{
e.printStackTrace();
}
// 歇息 片刻
try { sleep(100); }catch(Exception e) {}
// 显示结果 ( 更新 UI )
SwingUtilities.invokeLater( ()->{
done();
});
}
// 处理任务
protected void doInBackground() throws Exception
{
InputStream inputStream = null;
OutputStream outputStream = null;
long lastTime = System.currentTimeMillis();;
long fileSize = srcFile.length(); // 文件大小
long count = 0; // 已经完成的字节数
try {
inputStream = new FileInputStream(srcFile);
outputStream = new FileOutputStream(dstFile);
byte[] buf = new byte[8192]; // 每次最多读取8KB
while(true)
{
int n = inputStream.read(buf); // 从源文件读取数据
if(n <= 0) break; // 已经读完
outputStream.write(buf, 0, n); // 写入到目标文件
// 计数及显示进度
count += n; // 已经拷贝完成的字节数
long now = System.currentTimeMillis();
if(now - lastTime > 500) // 每500毫秒更新一次进度
{
lastTime = now;
updateProgress(count , fileSize);
Thread.sleep(50); // 不要老是占用CPU
}
}
}
finally {
// 出异常时,确保所有的文件句柄被关闭
try { inputStream.close();}catch(Exception e) {}
try { outputStream.close();} catch(Exception e) {}
}
}
// 更新进度
protected void updateProgress(long count, long total)
{
SwingUtilities.invokeLater( ()->{
if(waitDialog!=null)
{
// 设置进度条的进度(0-100)
int percent = (int)(100 * count / total) ;
waitDialog.setProgress( percent );
}
});
}
// 任务完成后,更新界面显示
protected void done()
{
// 关闭对话框
if(waitDialog!=null)
{
waitDialog.setVisible(false);
waitDialog = null;
}
}
}
}
package my;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import javax.swing.BorderFactory;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
/* 提示等待对话框
*
* 无标题栏 setUndecorated(true)
*
* 对话框里没有任何关闭按钮,所以用户不能手工关闭,只能等待操作结束
*
*/
public class WaitDialog extends JDialog
{
public JLabel display = new JLabel("请等待...");
public JProgressBar pbar = new JProgressBar(); // 进度条控件,用于显示进度
public WaitDialog(JFrame owner)
{
super(owner, "", true);
// 去掉标题栏
this.setUndecorated(true);
// 布局
JPanel root = new JPanel();
this.setContentPane(root);
root.setLayout(new BorderLayout());
// 背景
root.setOpaque(true);
root.setBackground(new Color(0xF4F4F4));
Border b1 = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
Border b2 = BorderFactory.createEmptyBorder(4, 4, 4, 4);
Border b3 = BorderFactory.createCompoundBorder(b1, b2);
root.setBorder(b3);
// 文字提示
display.setHorizontalAlignment(SwingConstants.CENTER);
root.add(display, BorderLayout.CENTER);
// 进度条 (0-100)
pbar.setMinimum(0); // 最小值
pbar.setMaximum(100); // 最大值
pbar.setPreferredSize(new Dimension(0,20)); // 高度设为20
root.add(pbar, BorderLayout.PAGE_END);
// 设置大小
this.setSize(200, 80);
}
// 设置进度 (0-100)
public void setProgress(int n)
{
pbar.setValue(n);
}
public void exec()
{
// 相对owner居中显示
Rectangle frmRect = this.getOwner().getBounds();
int width = this.getWidth();
int height = this.getHeight();
int x = frmRect.x + (frmRect.width - width) / 2;
int y = frmRect.y + (frmRect.height - height) / 2;
this.setBounds(x, y, width, height);
// 显示窗口 ( 阻塞 ,直接对话框窗口被关闭)
this.setVisible(true);
}
}
运行结果:可以显示进度条
Zip查看目录
- 日常工作中可能需要查看Zip压缩文件的目录,给定一个ZIP文件,查看ZIP目录信息;
- java.util.zip.ZipFile :一个可以操作zip文件的类。熟悉一下它的API的使用方法
ZipFile zipFile = new ZipFile(…)
….
zipFile.close();
- 查看目录
Enumeration<?> entries = zipFile.entries();
while(entries.hasMoreElements())
{
ZipEntry entry = (ZipEntry) entries.nextElement();
//....
}
- JDK里支持对ZIP操作,但不支持RAR
示例代码
package my;
import java.io.File;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class TestUnzip
{
public static void main(String[] args) throws Exception
{
// 准备一个ZIP文件
// 注意:文件的位置,直接放在项目根目录下
File scrFile = new File("af-swing.zip");
// 在Windows指定GBK,在Linux一般为UTF-8
// 若字符集不匹配,则文件名和路径里的中文将是乱码
ZipFile zipFile = new ZipFile(scrFile, Charset.forName("GBK"));
int fileCount = 0;
long totalSize = 0;
Enumeration<?> entries = zipFile.entries();
while(entries.hasMoreElements())
{
ZipEntry entry = (ZipEntry) entries.nextElement();
if(entry.isDirectory())
{
// 该项是目录
System.out.println("** " + entry.getName());
}
else
{
// 该项是文件
System.out.println(" " + entry.getName());
fileCount += 1; // 文件个数
totalSize += entry.getSize();
}
}
System.out.println("-----------------------");
System.out.println("文件总数: " + fileCount);
System.out.println("文件总数: " + totalSize);
zipFile.close();
}
}
解压缩
- 使用 ZipFile 的API实现从zip文件中提取出文件内容,将所有文件解压缩到指定目录。
- InputStream inputStream = zipFile.getInputStream(entry);
- entry.getName() 可以获取文件的路径
示例代码
package my;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class TestUnzip
{
public static void main(String[] args) throws Exception
{
// 准备一个ZIP文件
// 注意:文件的位置,直接放在项目根目录下
File scrFile = new File("af-swing.zip");
// 指定解压缩的目标位置
File dstDir = new File("c:/example/");
dstDir.mkdirs();
// 在Windows指定GBK,在Linux一般为UTF-8
// 若字符集不匹配,则文件名和路径里的中文将是乱码
ZipFile zipFile = new ZipFile(scrFile, Charset.forName("GBK"));
// 遍历每一条
Enumeration<?> entries = zipFile.entries();
while (entries.hasMoreElements())
{
ZipEntry entry = (ZipEntry) entries.nextElement();
if (entry.isDirectory()) continue;
System.out.println("处理文件:" + entry.getName());
// entry.getName() 获取条目的路径
File dstFile = new File(dstDir, entry.getName());
dstFile.getParentFile().mkdirs(); // 创建所在的子目录
// 从zip文件中解出数据
InputStream inputStream = zipFile.getInputStream(entry);
OutputStream outputStream = new FileOutputStream(dstFile);
try
{
byte[] buf = new byte[4096];
while(true)
{
int n = inputStream.read(buf);
if(n <= 0)break;
outputStream.write(buf, 0, n);
}
}
finally
{
// 确保文件被关闭
try{ inputStream.close(); } catch (Exception e){}
try{ outputStream.close(); } catch (Exception e){}
}
}
// 最后要记得关闭文件
zipFile.close();
}
}
综合Demo
- 结合短任务与显示zip文件目录
- 结合长任务与解压缩Zip文件
页面布局MyFrame.java
public class MyFrame extends JFrame
{
JPanel root = new JPanel(); // Content Pane
JTable table ; // 表格
DefaultTableModel tableModel ; // 表格的Model
WaitDialog waitDialog = null;
File srcFile; // 选中的*.zip文件
ZipInfo zipInfo; // zip文件的信息
public MyFrame(String title)
{
super(title);
// Content Pane
this.setContentPane(root);
root.setLayout(new BorderLayout());
// 初始化表格
this.initTable();
// 初始化工具栏
this.initToolBar();
}
private void initTable()
{
tableModel = new DefaultTableModel();
table = new JTable(tableModel){
@Override
public boolean isCellEditable(int row, int column)
{
// 直接重写 isCellEditable(),设为不可编辑
return false;
}
};
// 把 table 放在 Scroll Pane 以支持滚动条
JScrollPane scrollPane = new JScrollPane(table);
root.add(scrollPane, BorderLayout.CENTER);
// 添加到主界面
table.setFillsViewportHeight(true);
table.setRowSelectionAllowed(true); // 整行选择
table.setRowHeight(30);
// 列设置:添加5列
tableModel.addColumn (" ");
tableModel.addColumn ("名称");
tableModel.addColumn ("大小");
tableModel.addColumn ("修改时间");
// 列设置:自定义绘制
table.getColumnModel().getColumn(0).setCellRenderer(new IconColumnRenderer());
table.getColumnModel().getColumn(0).setMaxWidth(40); // 该列的宽度
table.getColumnModel().getColumn(2).setMaxWidth(120); // 该列的宽度
table.getColumnModel().getColumn(3).setMaxWidth(160); // 该列的宽度
table.getColumnModel().getColumn(3).setMinWidth(160); // 该列的宽度
}
private void initToolBar()
{
JToolBar toolBar = new JToolBar();
root.add(toolBar, BorderLayout.PAGE_START);
toolBar.setFloatable(false);
// 按钮
JButton b1 = new JButton("打开");
b1.setFocusPainted(false);
toolBar.add(b1);
b1.addActionListener( (e)->{
onFileOpen();
});
// 按钮
JButton b2 = new JButton("解压缩");
b1.setFocusPainted(false);
toolBar.add(b2);
b2.addActionListener( (e)->{
onFileExtract();
});
}
// '打开' 按钮
private void onFileOpen()
{
// Swing入门篇 13.3讲
JFileChooser chooser = new JFileChooser();
FileNameExtensionFilter filter = new FileNameExtensionFilter("ZIP文件", "zip");
chooser.setFileFilter(filter);
chooser.setCurrentDirectory(new File(".")); // 指定初始显示目录为当前目录
int ret = chooser.showOpenDialog(this);
if (ret == JFileChooser.APPROVE_OPTION)
{
this.srcFile = chooser.getSelectedFile();
// 启动一个工作线程
ZipInfoTask task = new ZipInfoTask();
task.execute(srcFile);
waitDialog = new WaitDialog(this);
waitDialog.exec();
}
}
// '解压缩' 按钮
private void onFileExtract()
{
if(this.srcFile == null) return; // 尚未打开源文件
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int ret = chooser.showOpenDialog(this);
if (ret == JFileChooser.APPROVE_OPTION)
{
// 结果为: 已经存在的一个目录
File dir = chooser.getSelectedFile();
ZipExtractTask task = new ZipExtractTask(this);
task.execute(this.srcFile, dir, zipInfo);
waitDialog = new WaitDialog(this);
waitDialog.exec();
}
}
// 显示所有的zip条目
private void showEntries(List<ZipEntry> entries)
{
tableModel.setNumRows(0); // 清空
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for(ZipEntry entry: entries)
{
Vector<Object> rowData = new Vector<>();
rowData.add(entry.isDirectory() );
rowData.add(entry.getName());
rowData.add(entry.getSize());
long lastModified = entry.getLastModifiedTime().toMillis();
rowData.add( sdf.format( lastModified ));
tableModel.addRow( rowData ); // 添加一行
}
}
// 短任务:负责解出zip文件中的条目信息
private class ZipInfoTask extends AfShortTask
{
//ZipInfo zipInfo ; // MyFrame.zipInfo
@Override
protected void doInBackground() throws Exception
{
File file = (File)this.args[0]; // 取得execute()的参数
ZipFile zipFile = new ZipFile(file, Charset.forName("GBK"));
try {
zipInfo = ZipInfo.from(zipFile);
}finally {
zipFile.close(); // 记得关闭ZipFile
}
zipInfo.sort(); // 排序:显示的时候文件夹在前显示
}
@Override
protected void done()
{
// 判断解压缩过程有没有出错
if(this.err != null) // this.err是doInBackground()里抛出的异常
{
this.err.printStackTrace();
return;
}
// 关闭对话框
if(waitDialog!=null)
{
waitDialog.setVisible(false);
waitDialog = null;
}
// 把条目显示到表格里
showEntries( zipInfo.entries);
}
}
}
负责解压缩的类ZipExtractTask,长任务、继承线程实现;
package my;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.swing.SwingUtilities;
public class ZipExtractTask extends Thread
{
MyFrame ui; // 窗口界面
File srcFile; // *.zip 文件
File dstDir; // 解压缩的目标目录
ZipInfo zipInfo;
public ZipExtractTask(MyFrame ui)
{
this.ui = ui;
}
public void execute(File srcFile, File dstDir, ZipInfo zipInfo)
{
this.srcFile = srcFile;
this.dstDir = dstDir;
this.zipInfo = zipInfo;
this.start();
}
@Override
public void run()
{
dstDir.mkdirs();
ZipFile zipFile = null;
try {
zipFile = new ZipFile(srcFile, Charset.forName("GBK"));
extract (zipFile);
}
catch(Exception e)
{
e.printStackTrace();
}finally {
try{ zipFile.close();}catch(Exception e) {}
}
// 最后要记得关闭文件
SwingUtilities.invokeLater( ()->{
done();
});
}
private void extract(ZipFile zipFile) throws Exception
{
long lastTime = System.currentTimeMillis();
int count = 0;
// 遍历每一条
Enumeration<?> entries = zipFile.entries();
while (entries.hasMoreElements())
{
ZipEntry entry = (ZipEntry) entries.nextElement();
if (entry.isDirectory())continue;
System.out.println("处理文件:" + entry.getName());
File dstFile = new File(dstDir, entry.getName());
dstFile.getParentFile().mkdirs(); // 创建所在的子目录
// 从zip文件中解出数据
InputStream inputStream = zipFile.getInputStream(entry);
OutputStream outputStream = new FileOutputStream(dstFile);
try
{
byte[] buf = new byte[4096];
while (true)
{
int n = inputStream.read(buf);
if (n <= 0) break;
outputStream.write(buf, 0, n);
// 为了增强演示效果,使之变慢一些
Thread.sleep(500);
// 每0.5秒更新一次界面
count += 1;
long now = System.currentTimeMillis();
if(now - lastTime > 500)
{
lastTime = now;
showProgress( count , zipInfo.fileCount);
Thread.sleep(50);
}
}
} finally
{
// 确保文件被关闭
try { inputStream.close();} catch (Exception e){}
try { outputStream.close(); } catch (Exception e){}
}
}
}
// 显示进度
private void showProgress(int count, int total)
{
final int progress = 100 *count / total;
SwingUtilities.invokeLater( ()->{
ui.waitDialog.setProgress( progress );
});
}
// 任务完成后的处理
private void done()
{
if(ui.waitDialog != null)
{
ui.waitDialog.setVisible(false);
ui.waitDialog = null;
}
}
}
Zip压缩文件的信息类ZipInfo.java
package my;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class ZipInfo
{
public int fileCount; // zip文件里的文件个数
public long totalSize; // zip文件里所有文件的总大小
List<ZipEntry> entries = new ArrayList<>();
// 显示的时候将文件夹排在前面,文件排在后面
public void sort()
{
}
// 工具方法: 从ZipFile中获取目录信息
public static ZipInfo from(ZipFile zipFile) throws Exception
{
ZipInfo info = new ZipInfo();
Enumeration<?> entries = zipFile.entries();
while (entries.hasMoreElements())
{
ZipEntry entry = (ZipEntry) entries.nextElement();
info.entries.add(entry);
if ( !entry.isDirectory())
{
info.fileCount += 1; // 文件个数
info.totalSize += entry.getSize();
}
}
return info;
}
}
非核心:图标的显示
package my;
import java.awt.Component;
import java.awt.Image;
import javax.imageio.ImageIO;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
import af.swing.image.AfImageView;
public class IconColumnRenderer extends AfImageView
implements TableCellRenderer
{
Image icFolder, icFile;
public IconColumnRenderer()
{
this.setScaleType(AfImageView.FIT_CENTER_INSIDE);
try {
icFolder = ImageIO.read( getClass().getResource("/icons/ic_folder_16.png"));
icFile = ImageIO.read( getClass().getResource("/icons/ic_file_16.png"));
}catch(Exception e)
{
e.printStackTrace();
}
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column)
{
// 设置图标
Boolean isDir = (Boolean)value;
if(isDir)
{
this.setImage( icFolder );
}
else
{
this.setImage( icFile );
}
// 背景设置
this.setOpaque(true);
if (isSelected) {
this.setBackground(table.getSelectionBackground());
this.setForeground(table.getSelectionForeground());
} else {
this.setBackground(table.getBackground());
this.setForeground(table.getForeground());
}
return this;
}
}
总结
- 介绍了长任务
- 介绍如何使用JDK的API查看Zip文件目录
- 介绍如何使用JDK的API解压缩Zip文件
- 虽然Swing技术早已经过时,但可以用来做一些小Demo,大型项目不会使用Swing技术做客户端