这俩天看《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类DownloadFiles类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; } }
运行结果: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; } }