您的位置:首页 > 编程语言 > Java开发

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

2012-02-14 23:54 671 查看
这俩天看《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;
}
}
运行结果:






                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息