自制簡單的Java下載器——來自《Java高級編程》的一個關於線程的例子(帶上部分註釋)

這倆天看《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;
	}
}
運行結果:




發佈了62 篇原創文章 · 獲贊 29 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章