代码改变世界

自制简单的Java下载器——来自《Java高级编程》的一个关于线程的例子(带上部分注释)

2012-02-14 23:54  java线程例子  阅读(482)  评论(0编辑  收藏  举报

这俩天看《Java高级编程》,看到下面这例子,觉得挺适合新手学习Thread的,所以记录下来,供向我这样的菜鸟学习学习,大牛可以直接忽略。

想法:完善并提高此程序的功能,做个专属自己的下载器。(2012/02/14 23:56 )

此程序主要有3个类:Downloader、DownloadManager、DownloadFiles。

1、Downloader:读取并写入数据

2、DownloadManager:主要用于控制下载,有开始、暂停、恢复、停止等功能

3、DownloadFiles:用于在文本框中输入URL并创建对应的DownloadManager类的实例

涉及到的知识点有:线程(Thread)、同步(synchronized)、I/O流、布局管理器(主要是GridBagLayout和它的约束GridBagConstraints)

Downloader类

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
/**
 * 读取并写入数据
 *
 */
public class Downloader extends JPanel implements Runnable{
	private static final long serialVersionUID = 216695025314371191L;
	private static final int BUFFER_SIZE = 1000;//byte数组的大小
	protected URL downloadURL;//所下载资源的URL
	protected InputStream inputStream;//字节输入流的所有类的超类
	protected OutputStream outputStream;//字节输出流的所有类的超类
	protected byte[] buffer;//缓冲区数组 buffer中
	
	protected int fileSize;//文件的大小
	protected int bytesRead;//已经读取的字节数
	
	protected JLabel urlLabel;//放置URL的JLabel
	protected JLabel sizeLabel;//放置文件大小的JLabel
	protected JLabel completeLabel;//放置已经下载大小的JLabel
	protected JProgressBar progressBar;//进度条

	protected boolean stopped = false;//是否停止下载的标志
	protected boolean sleepScheduled = false;//是否暂停一段时间的标志。
	protected boolean suspended = false;//线程是否挂起

	public final static int SLEEP_TIME = 5 * 1000;//暂停5秒

	protected Thread thisThread;//当前线程
	public static ThreadGroup downloaderGroup = new ThreadGroup("Donwload Threads");//线程组
	public Downloader(URL url, FileOutputStream fos) throws IOException {
		downloadURL = url;
		outputStream = fos;
		bytesRead = 0;
		//URLConnection构造一个到指定 URL 的 URL 连接。
		URLConnection urlConnection = downloadURL.openConnection();
		fileSize = urlConnection.getContentLength();//文件长度
		
		if(fileSize == -1){
			throw new FileNotFoundException(url.toString());
		}
		//在创建 BufferedInputStream 时,会创建一个内部缓冲区数组。
		inputStream = new BufferedInputStream(urlConnection.getInputStream());
		buffer = new byte[BUFFER_SIZE];
		thisThread = new Thread(downloaderGroup, this);
		buildLayout();
	}

	private void buildLayout() {
		JLabel label;
		setLayout(new GridBagLayout());
		GridBagConstraints gbc = new GridBagConstraints();
		//组件的显示区域大于它所请求的显示区域的大小时使用此字段。HORIZONTAL:在水平方向而不是垂直方向上调整组件大小。
		gbc.fill = GridBagConstraints.HORIZONTAL;
		//insets组件与其显示区域边缘之间间距的最小量
		gbc.insets = new Insets(5 ,10, 5, 10);
		//指定包含组件的显示区域开始边的"单元格",其中行的第一个单元格为 gridx=0。
		gbc.gridx = 0;
		label = new JLabel("地址:",  JLabel.LEFT);
		add(label, gbc);
		
		label = new JLabel("进度:",  JLabel.LEFT);
		add(label, gbc);
		
		label = new JLabel("已经下载:",  JLabel.LEFT);
		add(label, gbc);
		
		gbc.gridx = 1;
		//gridwidth:指定组件显示区域的某一行中的单元格数。  REMAINDER:指定此组件是其行或列中的最后一个组件
		gbc.gridwidth = GridBagConstraints.REMAINDER;
		//weightx:指定如何分布额外的水平空间。 
		//如果得到的布局在水平方向上比需要填充的区域小,那么系统会将额外的空间按照其权重比例分布到每一列。
		//权重为零的列不会得到额外的空间。 
		gbc.weightx = 1;
		urlLabel = new JLabel(downloadURL.toString());
		add(urlLabel, gbc);
		
		progressBar = new JProgressBar(0, fileSize);
		//设置 stringPainted 属性的值
		//该属性确定进度条是否应该呈现进度字符串。
		progressBar.setStringPainted(true);
		add(progressBar, gbc);
		
		gbc.gridwidth = 1;
		completeLabel = new JLabel(Integer.toString(bytesRead));
		add(completeLabel, gbc);
		
		gbc.gridx = 2;
		gbc.weightx = 0;
		//当组件小于其显示区域时使用此字段。
		//它可以确定在显示区域中放置组件的位置。 
		gbc.anchor = GridBagConstraints.EAST;
		label = new JLabel("文件大小:", JLabel.LEFT);
		add(label, gbc);
		///指定包含组件的显示区域开始边的"单元格",其中行的第一个单元格为 gridx=0。
		gbc.gridx = 3;
		gbc.weightx = 1;
		sizeLabel = new JLabel(Integer.toString(fileSize));
		add(sizeLabel, gbc);
	}
	public void run() {
		performDownload();
	}
	/**
	 * 负责执行下载的方法。
	 */
	private void performDownload() {
		int byteCount;
		//刷新进度条和completeLabel:是AWT时间线程与下载线程同步
		Runnable progressBarUpdate = new Runnable(){
			public void run() {
				progressBar.setValue(bytesRead);
				completeLabel.setText(Integer.toString(bytesRead));
			}
		};
		while((bytesRead < fileSize) && (!isStopped())){
			//是否暂停
			if(isSleepScheduled()){
				try {
					Thread.sleep(SLEEP_TIME);
				} catch (InterruptedException e) {
					setStopped(true);
					break;
				}
				setSleepScheduled(false);
			}
			try {
				//从输入流中读取一定数量的字节,并将其存储在缓冲区数组 buffer中
				//以整数形式返回实际读取的字节数。存储在缓冲区整数 byteCount中。
				byteCount = inputStream.read(buffer);
				if(byteCount == -1){
					setStopped(true);
					break;
				}else{
					outputStream.write(buffer, 0, byteCount);
					bytesRead += byteCount;
					//进度条的线程(创建多线程应用程序如果需要修改可视化组件,可以调用的SwingUtilities类的invokeLater()方法和invokeAndWait()方法)
					SwingUtilities.invokeLater(progressBarUpdate);
				}
			} catch (IOException e) {
				setStopped(true);
				JOptionPane.showMessageDialog(this,
						e.getMessage(),
						"I/O Error",
						JOptionPane.ERROR_MESSAGE);
				break;
			}
			//是否暂停
			synchronized(this){
				if(isSuspended()){
					try {
						//下载线程调用wait()方法后会隐式的放弃监控的所有权
						this.wait();
					} catch (InterruptedException e) {
						setStopped(true);
						break;
					}
					setSuspended(false);
				}
			}
			//测试当前线程是否已经中断。
			if(Thread.interrupted()){
				setStopped(true);
				break;
			}
		}
		try {
			//关闭流,断开与所下载文件的连接
			inputStream.close();
			outputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		//是否下载完了?
		if(bytesRead == fileSize){
			JOptionPane.showMessageDialog(null,
					"完成下载",
					"已下载完成!",
					JOptionPane.INFORMATION_MESSAGE);
			//System.exit(1);
		}
	}
	public synchronized void startDownload() {
		thisThread.start();
	}
	public synchronized void stopDownload() {
		thisThread.interrupt();
	}
	public synchronized void resumeDownloader() {
		//notify()和notifyAll()方法并不会让等待线程立即回复执行。
		//等待线程要回复执行,就必须先取得与线程同步的对象监控
		this.notify();
	}
	public synchronized void setStopped(boolean stopped) {
		this.stopped = stopped;
	}
	public synchronized boolean isStopped() {
		return stopped;
	}
	public synchronized void setSleepScheduled(boolean sleepScheduled) {
		this.sleepScheduled = sleepScheduled;
	}
	public synchronized boolean isSleepScheduled() {
		return sleepScheduled ;
	}
	public synchronized void setSuspended(boolean suspended) {
		this.suspended = suspended;
	}
	public synchronized boolean isSuspended() {
		return suspended;
	}
	public static void cancelAllAndWait(){
		//activeCount()返回线程组中活动线程的个数
		int count = downloaderGroup.activeCount();
		Thread[] threads = new Thread[count];
		//enumerate()将每个活动的线程的引用存入threads数组中。
		count = downloaderGroup.enumerate(threads);
		downloaderGroup.interrupt();
		for(int i = 0; i < count; i++){
			try {
				threads[i].join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	/*
	 * 注意:Thread类中的suspended()、resume()、stop()方法都是已经过时的。
	 * 这里也没有调用。而是手动实现对应的功能。
	 */
}
}



DownloadManager类
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;

import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
/**
 * 控制下载:开始、暂停、停止
 */
public class DownloadManager extends JPanel{
	private static final long serialVersionUID = 7917262241189749835L;
	protected Downloader downloader;
	protected JButton startButton;//开始
	protected JButton sleepButton;//暂停5秒
	protected JButton suspendButton;//暂停
	protected JButton resumeButton;//恢复
	protected JButton stopButton;//停止
	
	public DownloadManager(URL url, FileOutputStream fos) throws IOException{ 
		downloader = new Downloader(url, fos);
		buildLayout();
		Border border = new BevelBorder(BevelBorder.RAISED);
		String name = url.toString();
		int index = name.lastIndexOf('/');
		border = new TitledBorder(border, name.substring(index + 1)); 
		setBorder(border);
	}
	private void buildLayout() {
		setLayout(new BorderLayout());
		//BevelBorder:该类实现简单的双线斜面边框。 
		downloader.setBorder(new BevelBorder(BevelBorder.RAISED));
		add(downloader, BorderLayout.CENTER);
		add(getButtonPanel(), BorderLayout.SOUTH);
	}
	//放置按钮的JPanel
	private JPanel getButtonPanel() {
		JPanel outerPanel;//为了调整好布局。 
		JPanel innerPanel = new JPanel();
		innerPanel.setLayout(new GridLayout(1, 5 , 10, 0));
		
		startButton = new JButton("开始");
		startButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				startButton.setEnabled(false);
				sleepButton.setEnabled(true);
				resumeButton.setEnabled(false);
				suspendButton.setEnabled(true);
				stopButton.setEnabled(true);
				downloader.startDownload();
			}
		});
		innerPanel.add(startButton);
		sleepButton = new JButton("暂定5秒");
		sleepButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				downloader.setSleepScheduled(true);
			}
			
		});
		innerPanel.add(sleepButton);
		
		suspendButton = new JButton("暂停");
		suspendButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				suspendButton.setEnabled(false);
				resumeButton.setEnabled(true);
				stopButton.setEnabled(true);
				downloader.setSuspended(true);
			}
			
		});
		innerPanel.add(suspendButton);
		
		resumeButton = new JButton("恢复下载");
		resumeButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				resumeButton.setEnabled(false);
				suspendButton.setEnabled(true);
				stopButton.setEnabled(true);
				downloader.resumeDownloader();
			}
			
		});
		innerPanel.add(resumeButton);
		
		stopButton = new JButton("停止");
		stopButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				stopButton.setEnabled(false);
				sleepButton.setEnabled(false);
				suspendButton.setEnabled(false);
				resumeButton.setEnabled(false);
				startButton.setEnabled(true);
				downloader.stopDownload();
			}
			
		});
		innerPanel.add(stopButton);
		
		outerPanel = new JPanel();
		outerPanel.add(innerPanel);
		return outerPanel;
	}
}
DownloadFiles类
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileOutputStream;
import java.net.URL;
import java.net.URLConnection;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.UIManager;
/**
 * 用于在文本框中输入URL并创建对应的DownloadManager类的实例
 */
public class DownloadFiles extends JPanel{
	
	private static final long serialVersionUID = 2575460962137056640L;
	protected JPanel listPanel;//放置各个下载的面板
	protected GridBagConstraints constraints;//指定使用 GridBagLayout类布置的组件的约束。
	protected final String filepath = "D:/";//所下载的文件保存的路径
	private int taskCount = 0;
	static JFrame frame;
	public static void main(String[] args){
		frame = new JFrame("From Cannel_2020's blog(csdn)");
		DownloadFiles df = new DownloadFiles();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().add(df);
		frame.setSize(700, 400);
		frame.setVisible(true);
	}
	public DownloadFiles(){
		try {
			//设置外观
			//UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		    UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");   
	        //UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");   
	        //UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());   
	        //UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");   
		} catch (Exception e) {
			e.printStackTrace();
		}
		setLayout(new BorderLayout());
		listPanel = new JPanel();
		listPanel.setLayout(new GridBagLayout());
		constraints = new GridBagConstraints();
		constraints.gridx = 0;
		constraints.weightx = 1;
		constraints.fill = GridBagConstraints.HORIZONTAL;
		constraints.anchor = GridBagConstraints.NORTH;
		JScrollPane jsp = new JScrollPane(listPanel);
		add(jsp, BorderLayout.CENTER);
		
		add(getAddURLPanel(), BorderLayout.SOUTH);
	}
	//地址栏、两按钮
	private JPanel getAddURLPanel() {
		JPanel panel = new JPanel();
		JLabel label = new JLabel("URL");
		final JTextField textField = new JTextField(30);
		final JButton downloadButton = new JButton("点击下载");
		
		ActionListener actionListener = new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				new Thread(){
					public void run() {
						downloadButton.setText("正在连接");
						downloadButton.setEnabled(false);
						if(createDownloader(textField.getText())){
							textField.setText("");
							++taskCount;
							frame.setTitle("共有:"+taskCount+"个下载任务");
							revalidate();
						}
						downloadButton.setText("点击下载");
						downloadButton.setEnabled(true);
					}
				}.start();
				
			}
		};
		textField.addActionListener(actionListener);//加了actionListener监听器,按Enter便会下载
		downloadButton.addActionListener(actionListener);
		
		JButton clearAll = new JButton("清除所有");
		clearAll.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				Downloader.cancelAllAndWait();
				listPanel.removeAll();
				revalidate();
				repaint();
			}
		});
		
		panel.add(label);
		panel.add(textField);
		panel.add(downloadButton);
		panel.add(clearAll);
		return panel;
	}

	private boolean createDownloader(String url) {
		try {
			URL downloadURL = new URL(url);
			URLConnection urlconnection = downloadURL.openConnection();
			int length = urlconnection.getContentLength();
			if(length < 0){
				throw new Exception("无法确定所下载文件的长度!");
			}
			int index = url.lastIndexOf('/');
			
			File file=new File(filepath+url.substring(index + 1));
			if(file.exists()){
				JOptionPane.showMessageDialog(this, "该文件已经存在",
						"无法下载", JOptionPane.ERROR_MESSAGE);
				return false;
			}
			FileOutputStream fos = new FileOutputStream(file);//filepath+url.substring(index + 1)
			
			//BufferedOutputStream bos = new BufferedOutputStream(fos);
			DownloadManager dm = new DownloadManager(downloadURL, fos);
			listPanel.add(dm, constraints);
			return true;
		} catch (Exception e) {
			JOptionPane.showMessageDialog(this, "该资源无法下载。",
					"无法下载!", JOptionPane.ERROR_MESSAGE);
		}
		return false;
	}
}
运行结果: