欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java swing 带界面和进度条的多线程下载器实现

程序员文章站 2024-02-02 12:30:52
...

前言

因为大作业要做浏览器,浏览器要带下载器,所以现在先实现一个带界面的多线程下载器类。

多线程下载器原理

之前写过一个的【Java URLConnection类 实现多线程下载文件】,只是那个demo比较简陋。。。

今天我们来加上图形界面,其实下载部分的代码都是复用过来的。。。。

要用到的swing组件

部分swing组件的介绍和【Java swing简易浏览器(其一)页面显示,超链接跳转与手动输入URL跳转】中的说明相同,这里简单说明一下

JFrame

主窗体,windows窗口,可缩放等等

JTextField

文字框输入,我们需要用户输入或者改变

  1. 下载的url
  2. 保存地址
  3. 文件名

可以通过.set / get Text方法设置或者取得里面的文字

JButton

按钮组件,我们点击后,开始创建下载线程并且开始下载。

可以简单的为一个JButton对象绑定被点击时候的事件

// 开始下载按钮 注册事件 点击的时候创建新下载线程
JButton startBtn = new JButton("开始下载");
startBtn.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent e) {
		// 按钮按下的时候执行此处代码
	}
});

JLabel

一个文字显示的组件,可以支持显示html格式,我们用于显示一些提示信息

JProgressBar

这次的重点,进度条组件,使用方便,在构造函数的时候,指定最小值,最大值

JProgressBar bar = new JProgressBar(0, 100);

然后可以通过.setValue来改变当前进度条的值

布局

Java swing 带界面和进度条的多线程下载器实现
JFrame使用【网格布局】,通过以下代码就可以实现一个9行1列的网格

// 主窗体
JFrame jf = new JFrame("下载器");
jf.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
jf.setSize(512, 321);
jf.setLayout(new GridLayout(9, 1));

实现

因为进度条需要调用组件,所以我们创建一个类叫MultiThreadDownloader,将所有组件都设置为成员变量

此外我们还要存一个下载进程,方便我们查看下载进度并且更新进度条,因为所有下载进程共享一个进度,我们保留其中一个下载进程,就可以轻松获取了。

public class MultiThreadDownloader {
	
	String url;				// 目标url
	String savePath;		// 本地保存路径
	String fileName;		// 文件名
	int tnum = 5;			// 默认线程数目
	long totalLength;		// 总大小
	DownloadThread dth;		// 一个下载线程,用于查看下载进度
	JLabel  urlInputText;	// 文字:目标对象url
	JTextField urlInput;	// url输入框
	JLabel  pathInputText;	// 文字:本地保存路径
	JTextField pathInput;	// 保存路径输入框
	JLabel  nameInputText;	// 文字:文件名
	JTextField nameInput;	// 文件名输入框
	JLabel  barTex;			// 文字:进度条
	JProgressBar bar;		// 进度条
	JButton startBtn;		// 开始下载按钮
	ExecutorService pool = Executors.newCachedThreadPool();	// 线程池
	
	MultiThreadDownloader() {
		
	}
	
	MultiThreadDownloader(String url, String savePath, String fileName, int tnum) {
		this.url = url;
		this.tnum = tnum;
		this.savePath = savePath;
		this.fileName = fileName;
	}
...
下面是类的其他方法实现
}

我们编写一个download方法,该方法初始化我们的界面并且显示,除此之外,还为按钮注册按下事件, 事件内容里面执行的代码就是创建下载线程并且开始下载。

public void download() throws Exception {
		
	// 初始化组件,利用默认内容
	// url输入
	urlInputText = new JLabel ("目标对象URL");
	urlInput = new JTextField(url);
	// 路径输入
	pathInputText = new JLabel ("本地保存路径");
	pathInput = new JTextField(savePath);
	// 文件名输入
	nameInputText = new JLabel ("文件名");
	nameInput = new JTextField(fileName);
	// 进度条
	JLabel  barText = new JLabel ("进度条");
	bar = new JProgressBar(0, 100);
	
	// 开始下载按钮 注册事件 点击的时候创建新下载线程
	startBtn = new JButton("开始下载");
	startBtn.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
			startBtn.setEnabled(false);	// 按钮禁用
			// 获得用户实际输入的内容
			savePath = pathInput.getText();
			fileName = nameInput.getText();
			url = urlInput.getText();
			try {
				totalLength = new URL(url).openConnection().getContentLength();
				bar.setMaximum((int)totalLength);
				long eachLength = totalLength / tnum;
				// 拓展名
				String ext = url.substring(url.lastIndexOf("."));
				// 创建下载线程
				for(int i=0; i<tnum; i++) {		
					long st = eachLength * i;
					long ed = eachLength * (i+1);
					if(i == tnum-1) {
						ed=Math.max(ed, totalLength);
					}
					// 建立URL连接
					URLConnection con = new URL(url).openConnection();
					con.setRequestProperty("Range", "bytes="+String.valueOf(st)+"-"+String.valueOf(ed));
					con.connect();
					// 打开文件流
					RandomAccessFile rf = new RandomAccessFile(savePath+fileName+ext, "rw");
					rf.seek(st);	// 文件流跳转到对应位置
					// 创建下载线程
					DownloadThread d =new DownloadThread(con, rf);
					pool.submit(d);
	
					// 保存最后一个线程 方便查看进度
					if(i==tnum-1) {
						dth = d;
					}
				}
				// 创建一个每500ms更新进度条的线程并且跑起来
				pool.submit(new Thread() {
					public void run() {
						while(true) {
							bar.setValue((int)dth.allcur);
							if(dth.allcur >= totalLength) {
								startBtn.setText("下载完成");
								break;
							}
							try {
								this.sleep(500);
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
					}
				});
				// 关闭线程池
				pool.shutdown();
			} catch(Exception e1) {
				e1.printStackTrace();
			}
		}
	});
	// 开始下载按钮 注册事件 -- 结束
	
	// 主窗体
	JFrame jf = new JFrame("下载器");
	jf.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
	jf.setSize(512, 321);
	jf.setLayout(new GridLayout(9, 1));
	jf.add(urlInputText);
	jf.add(urlInput);
	jf.add(pathInputText);
	jf.add(pathInput);
	jf.add(nameInputText);
	jf.add(nameInput);
	jf.add(barText);
	jf.add(bar);
	jf.add(startBtn);
	jf.show();
}

main函数只需创建相应的下载器,调用download方法即可。

这里我在我的个人网址上提供了两个链接用于测试下载,分别是稍大的pdf文件和小型txt文件。

http://www.szulrl.cn/browserTest/PDFFILE.zip
http://www.szulrl.cn/browserTest/TXTFILE.zip

public static void main(String[] args) throws Exception {
	MultiThreadDownloader dl = new MultiThreadDownloader(
			"http://www.szulrl.cn/browserTest/PDFFILE.zip", 
			"E:/MyEclipse/WorkSpace/Hello/src/homework/", 
			"下载",
			3);
	dl.download();
}

完整代码

/*
 * @Author : @李若龙 2018171028 @SZU @CSSE
 */
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.html.*;

class DownloadThread extends Thread {
	
	URLConnection con;
	RandomAccessFile rf;
	
	public static volatile long allcur = 0;
	public static boolean isReady = false;
	
	public DownloadThread() {
		
	}
	
	public DownloadThread(URLConnection con, RandomAccessFile rf) {
		this.con = con;
		this.rf = rf;
	}
	
	public void run() {
		try {
			// 获取输入输出流
			InputStream is = con.getInputStream();
			//System.out.printf("线程 获取文件成功\n");
			
			// 输入流内容写入输出流
			byte[] buf = new byte[1024];
			int len = -1;
			
			while((len=is.read(buf)) != -1) {
				rf.write(buf, 0, len);
				synchronized(new Object()) {
					allcur += (long)len;
				}
				// System.out.printf("%d / %d \n", allcur, 29210163);
			}
			rf.close();
			
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
}

public class MultiThreadDownloader {
	
	String url;				// 目标url
	String savePath;		// 本地保存路径
	String fileName;		// 文件名
	int tnum = 5;			// 默认线程数目
	long totalLength;		// 总大小
	DownloadThread dth;		// 一个下载线程,用于查看下载进度
	JLabel  urlInputText;	// 文字:目标对象url
	JTextField urlInput;	// url输入框
	JLabel  pathInputText;	// 文字:本地保存路径
	JTextField pathInput;	// 保存路径输入框
	JLabel  nameInputText;	// 文字:文件名
	JTextField nameInput;	// 文件名输入框
	JLabel  barTex;			// 文字:进度条
	JProgressBar bar;		// 进度条
	JButton startBtn;		// 开始下载按钮
	ExecutorService pool = Executors.newCachedThreadPool();	// 线程池
	
	MultiThreadDownloader() {
		
	}
	
	MultiThreadDownloader(String url, String savePath, String fileName, int tnum) {
		this.url = url;
		this.tnum = tnum;
		this.savePath = savePath;
		this.fileName = fileName;
	}
	
	public void download() throws Exception {
		
		// 初始化组件,利用默认内容
		// url输入
		urlInputText = new JLabel ("目标对象URL");
		urlInput = new JTextField(url);
		// 路径输入
		pathInputText = new JLabel ("本地保存路径");
		pathInput = new JTextField(savePath);
		// 文件名输入
		nameInputText = new JLabel ("文件名");
		nameInput = new JTextField(fileName);
		// 进度条
		JLabel  barText = new JLabel ("进度条");
		bar = new JProgressBar(0, 100);
		
		// 开始下载按钮 注册事件 点击的时候创建新下载线程
		startBtn = new JButton("开始下载");
		startBtn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				startBtn.setEnabled(false);	// 按钮禁用
				// 获得用户实际输入的内容
				savePath = pathInput.getText();
				fileName = nameInput.getText();
				url = urlInput.getText();
				try {
					totalLength = new URL(url).openConnection().getContentLength();
					bar.setMaximum((int)totalLength);
					long eachLength = totalLength / tnum;
					// 拓展名
					String ext = url.substring(url.lastIndexOf("."));
					// 创建下载线程
					for(int i=0; i<tnum; i++) {		
						long st = eachLength * i;
						long ed = eachLength * (i+1);
						if(i == tnum-1) {
							ed=Math.max(ed, totalLength);
						}
						// 建立URL连接
						URLConnection con = new URL(url).openConnection();
						con.setRequestProperty("Range", "bytes="+String.valueOf(st)+"-"+String.valueOf(ed));
						con.connect();
						// 打开文件流
						RandomAccessFile rf = new RandomAccessFile(savePath+fileName+ext, "rw");
						rf.seek(st);	// 文件流跳转到对应位置
						// 创建下载线程
						DownloadThread d =new DownloadThread(con, rf);
						pool.submit(d);
						if(i==tnum-1) {
							dth = d;
						}
					}
					// 创建一个每500ms更新进度条的线程
					pool.submit(new Thread() {
						public void run() {
							while(true) {
								//System.out.printf("%d / %d \n", dth.allcur, totalLength);
								bar.setValue((int)dth.allcur);
								if(dth.allcur >= totalLength) {
									startBtn.setText("下载完成");
									break;
								}
								try {
									this.sleep(500);
								} catch (InterruptedException e) {
									e.printStackTrace();
								}
							}
						}
					});
					// 关闭线程池
					pool.shutdown();
				} catch(Exception e1) {
					e1.printStackTrace();
				}
			}
		});
		// 开始下载按钮 注册事件 点击的时候创建新下载线程 -- 结束
		
		// 主窗体
		JFrame jf = new JFrame("下载器");
		jf.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
		jf.setSize(512, 321);
		jf.setLayout(new GridLayout(9, 1));
		jf.add(urlInputText);
		jf.add(urlInput);
		jf.add(pathInputText);
		jf.add(pathInput);
		jf.add(nameInputText);
		jf.add(nameInput);
		jf.add(barText);
		jf.add(bar);
		jf.add(startBtn);
		jf.show();
		
	}
	
	public static void main(String[] args) throws Exception {
		MultiThreadDownloader dl = new MultiThreadDownloader(
				"http://www.szulrl.cn/browserTest/PDFFILE.zip", 
				"E:/MyEclipse/WorkSpace/Hello/src/homework/", 
				"下载",
				3);
		dl.download();
	}
}

演示

Java swing 带界面和进度条的多线程下载器实现