一文必懂-长任务与解压缩Zip压缩文件

本文介绍了如何使用Java Swing创建一个带有进度显示的长任务,如解压缩Zip文件。通过创建线程和进度对话框,实现了在用户界面中实时更新解压缩进度。同时,详细讲解了查看Zip文件目录和解压缩文件的API用法,提供了相关的代码示例。
摘要由CSDN通过智能技术生成

长任务

  • 需要较长的时间完成
  • 需要显示进度
  • 需要显示结果
  • 工作经常会有一些耗时间的操作,例如上传、下载、编码、解码 …

长任务练习

  • 拷贝文件时,当文件较大时(几个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;
	}
}

总结

  1. 介绍了长任务
  2. 介绍如何使用JDK的API查看Zip文件目录
  3. 介绍如何使用JDK的API解压缩Zip文件
  4. 虽然Swing技术早已经过时,但可以用来做一些小Demo,大型项目不会使用Swing技术做客户端
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值