這倆天看《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()方法都是已經過時的。
* 這裏也沒有調用。而是手動實現對應的功能。
*/
}
}
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;
}
}
運行結果: