1、package DownLoader.ui 中的类:APPMain、DownLoaderJFrame、MyProgressBar
2、package DownLoader.utils 中类:ClimbNetPage、DownLoader、DownLoaderTask、ImageUtil、Speeder
3、package DownLoaderTableMode 中的类:DownLoaderTableMode_Observer
4、packageMyDataBasic 中的类TestDataBasic
package DownLoader.ui 中的APPMain类:
package DownLoader.ui; import java.awt.Color; import java.awt.Font; import javax.swing.SwingUtilities; import javax.swing.UIManager; /** * 主程序入口 * @author Huangyujun * */ public class APPMain { public static void main(String[] args) { Font font1 = new Font("华文行楷", Font.BOLD, 18); Font font2 = new Font("微软黑体", Font.BOLD, 18); //字体的修饰 UIManager.put("TextField.font", font2); UIManager.put("Button.font", font1); UIManager.put("Table.font", font2); UIManager.put("TableHeader.font", font1); UIManager.put("TitledBorder.font", font1); //防止线程死锁 SwingUtilities.invokeLater(() -> new DownLoaderJFrame().setVisible(true) ); } }
package DownLoader.ui 中的DownLoaderJFrame类:
package DownLoader.ui; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.IOException; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JLayeredPane; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import DownLoader.utils.ClimbNetPage; import DownLoader.utils.DownLoader; import DownLoader.utils.ImageUtil; import DownLoaderTableMode.DownLoaderTableMode_Observer; /** * 下载器窗体 * * @author Huangyujun * */ public class DownLoaderJFrame extends JFrame { // 控件:文本框控件、添加按钮、表格、底部带(暂停、重置、取消、清空)按钮的控件 private JTextField urlTxt = new JTextField(45); private JButton addButton = new JButton("添加"); private JButton searchButton = new JButton("查询"); private JButton changeImageButton = null; //换肤按钮 private JTable tableDownLoaderInfo = new JTable(); private JButton pauseButton = new JButton("暂停"); private JButton cancelButton = new JButton("取消"); private JButton clearButton = new JButton("清空"); private JButton climbButton = new JButton("爬网页"); private JPopupMenu popMenu = new JPopupMenu(); // 右键弹出菜单 JMenuItem downloadMenuItem = new JMenuItem("下载"); // 下载菜单项 JMenuItem delMenuItem = new JMenuItem("删除"); // 删除菜单项 //表格模式 DownLoaderTableMode_Observer tableMode_Observer = new DownLoaderTableMode_Observer(); //图片路径数组 private String[] imagePath = {null, "D:\\EclispeProjects\\MyDownLoader\\photos\\xuehua.png", "D:\\EclispeProjects\\MyDownLoader\\photos\\flower.png", "D:\\EclispeProjects\\MyDownLoader\\photos\\shu.png" }; int imageIndex; //图片数组下标 int z; //图层在z轴上距离 /** 换肤按钮图片 */ private String changeImageButtonPath = "D:\\EclispeProjects\\MyDownLoader\\photos\\changeImaga.png"; /** 靠近换肤按钮图片 */ private String nearImageButtonPath = "D:\\EclispeProjects\\MyDownLoader\\photos\\nearImaga.png"; /** 按下换肤按钮图片 */ private String pressedImageButtonPath = "D:\\EclispeProjects\\MyDownLoader\\photos\\pressedImaga.png"; JPanel contentPane = null; // 内容面板 public DownLoaderJFrame() { // 设置标题 setTitle("一心下载"); // 内容面板 contentPane = (JPanel) getContentPane(); //设置大小,与背景图片一样大 setSize(1100, 700); //下载器背景风格简约,其中默认为系统背景图片 // setSize(imageBackground.getIconWidth(), imageBackground.getIconHeight()); //设置背景图片 setBackground(imagePath[0], 0); // 居中 setLocationRelativeTo(null); // 封装了其余初始化工作 initComponents(); // 事件 initEvents(); // 退出模式 setDefaultCloseOperation(EXIT_ON_CLOSE); } /** * 设置自定义背景图片 */ private void setBackground(String path, int z) { //获取背景图片 ImageIcon imageIcon = ImageUtil.getImage(path); //通过标签控件封装图片标签后,添加到布局控件 JLabel jLabelImage = new JLabel(imageIcon); //设置标签边界 jLabelImage.setBounds(0, 0, imageIcon.getIconWidth(), imageIcon.getIconHeight()); //添加至布局面板 JLayeredPane layoutPane = getLayeredPane(); layoutPane.add(jLabelImage, new Integer(Integer.MIN_VALUE + z)); //设置内容面板不透明为假 contentPane.setOpaque(false); } /** * 封装了其余初始化工作 */ private void initComponents() { // 顶部流动布局面板 JPanel topPane = new JPanel(); // 顶部面板添加文本框 topPane.add(urlTxt); // 顶部面板添加添加按钮 topPane.add(addButton); // 顶部面板添加查询按钮 topPane.add(searchButton); // 顶部面板添加换皮肤按钮 initChangeImage(topPane); // 中间下载信息面板,并且设置设置为流式布局 JPanel middlePane = new JPanel(new BorderLayout()); // 中间面板设置边框,并且添加标题 middlePane.setBorder(BorderFactory.createTitledBorder("下载内容")); // 中间面板添加带表格的滚动面板 middlePane.add(new JScrollPane(tableDownLoaderInfo)); // 设置表格行高 tableDownLoaderInfo.setRowHeight(30); // 菜单添加下载菜单项 popMenu.add(downloadMenuItem); // 菜单添加删除菜单项 popMenu.add(delMenuItem); // 表格添加右键菜单 tableDownLoaderInfo.add(popMenu); // 设置表格模式 setTableMode(tableDownLoaderInfo); // 创建进度条对象 MyProgressBar myProgressBar = new MyProgressBar(); // 为下载进度设置进度条 tableDownLoaderInfo.getColumn("下载进度").setCellRenderer(myProgressBar); // 设置进度条的数字为可见 myProgressBar.setStringPainted(true); // 底部面板 JPanel bottomPane = new JPanel(); // 底部面板添加暂停按钮、取消按钮、清空按钮、爬取按钮 bottomPane.add(pauseButton); bottomPane.add(cancelButton); bottomPane.add(clearButton); bottomPane.add(climbButton); //设置顶部布局面板为透明 topPane.setOpaque(false); //设置中间面板为透明 middlePane.setOpaque(false); //设置底部面板为透明 bottomPane.setOpaque(false); // 内容面板添加顶部面板,并且设置位置为顶部 contentPane.add(topPane, BorderLayout.NORTH); // 内容面板添加中间面板,并且设置位置为中间 contentPane.add(middlePane, BorderLayout.CENTER); // 内容面板添加底部面板,并且设置位置为底部 contentPane.add(bottomPane, BorderLayout.SOUTH); } /** * 换皮肤 * @param topPane */ private void initChangeImage(JPanel topPane) { //添加按钮 changeImageButton = new JButton(new ImageIcon(changeImageButtonPath)); //设置按钮的空间大小 changeImageButton.setPreferredSize(new Dimension(32, 32)); //设置按钮图片,鼠标接近,按下的图片切换 changeImageButton.setRolloverIcon(new ImageIcon(nearImageButtonPath)); changeImageButton.setPressedIcon(new ImageIcon(pressedImageButtonPath)); //取消掉按钮的边框 changeImageButton.setBorder(BorderFactory.createEmptyBorder()); //为按钮添加换肤事件 changeImageEvent(); //添加按钮到布局面板 topPane.add(changeImageButton); } /** * 换肤事件 */ private void changeImageEvent() { changeImageButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int imageIndex2 = imageIndex + 1; //图片数组下标 if(imageIndex2 > 3) { imageIndex = 0; imageIndex2 = 1; } imageIndex++; z++; //图层在z轴上距离+1 System.out.println(z); //设置更换图片 setBackground(imagePath[imageIndex2], z); } }); } /** * 事件 */ private void initEvents() { // 添加按钮添加事件 addButton.addActionListener(e -> addAction(e)); // 查询按钮添加事件 searchButton.addActionListener(e -> searchAction(e)); // 暂停按钮添加事件 pauseButton.addActionListener(e -> pauseAction(e)); // 取消按钮添加事件 cancelButton.addActionListener(e -> cancelAction(e)); // 清空按钮添加事件 clearButton.addActionListener(e -> clearAction(e)); // 爬取按钮添加事件 climbButton.addActionListener(e -> climbAction(e)); // 为表格添加右键菜单的事件 tableDownLoaderInfo.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON3) { // 右键鼠标 popMenu.show(tableDownLoaderInfo, e.getX(), e.getY()); } } }); // 下载菜单项添加事件 downloadMenuItem.addActionListener(e -> downloadAction(e)); // 删除菜单项添加事件 delMenuItem.addActionListener(e -> cancelAction(e)); } /** * 添加按钮的事件行为 * * @param e */ private void addAction(ActionEvent e) { // 获取文本框的内容 String netUrl = urlTxt.getText(); // 判断内容是否合法 if ("".equals(netUrl)) return; // if(!netUrl.startsWith("https://") || netUrl.startsWith("http://")) { // //弹出对话框 // JOptionPane.showMessageDialog(this, "本系统只支持Https协议!"); // return; // } // 测试用的路径 String localPath = "D:\\test\\" + netUrl.substring(netUrl.lastIndexOf("\\") + 1); // //本地路径 // String localPath = netUrl.substring(netUrl.lastIndexOf("/") + 1); // 创建下载器对象 DownLoader downloader = new DownLoader(netUrl, localPath, 5); // 调用表格的添加下载器的方法 tableMode_Observer.addDownloader(downloader); } /** * 查询事件行动 * * @param e */ private void searchAction(ActionEvent e) { // 获取输入框文本 String str = urlTxt.getText(); // 调用表格模式的查询方法 tableMode_Observer.findStrDownLoader(str); } /** * 下载事件行动 * @param e */ private void downloadAction(ActionEvent e) { // 判断是否选中了某一行 int row = tableDownLoaderInfo.getSelectedRow(); if (-1 == row) return; // 调用表格模式的下载方法 tableMode_Observer.download(row); } /** * 添加暂停与恢复事件的行为 * * @param e * @return */ private void pauseAction(ActionEvent e) { // 判断是否选中了某一行 int row = tableDownLoaderInfo.getSelectedRow(); if (-1 == row) { if ("暂停".equals(pauseButton.getActionCommand())) { pauseButton.setText("暂停"); return; } } else { if ("暂停".equals(pauseButton.getActionCommand())) { pauseButton.setText("开始"); // 调用表格模式中的暂停方法 tableMode_Observer.pause(row); } else if ("开始".equals(pauseButton.getActionCommand())) { pauseButton.setText("暂停"); // 调用表格模式中的恢复方法 tableMode_Observer.resume(row); } } } /** * 取消--删除选中的下载器事件行动 * * @param e */ private void cancelAction(ActionEvent e) { // 判断是否选中了某一行 int row = tableDownLoaderInfo.getSelectedRow(); if (-1 == row) return; // 调用表格模式中的取消删除方法 tableMode_Observer.deleteDownloader(row); } /** * 清空事件行动 * * @param e */ private void clearAction(ActionEvent e) { // 调用表格模式中的清空方法 tableMode_Observer.clearAllDownloader(); } private void climbAction(ActionEvent e) { // 获取文本框的内容 String netUrl = urlTxt.getText(); // 判断内容是否合法 if ("".equals(netUrl)) return; if (!netUrl.startsWith("https://") || netUrl.startsWith("http://")) { // 弹出对话框 JOptionPane.showMessageDialog(this, "本系统只支持Https协议!"); return; } //本地路径 String localPath = netUrl.substring(netUrl.lastIndexOf("/") + 1); // 调用爬取类的爬取方法 try { new ClimbNetPage(netUrl, localPath); } catch (IOException e1) { e1.printStackTrace(); } } /** * 设置表格模式 * * @param tableDownLoaderInfo2 */ private void setTableMode(JTable tableDownLoaderInfo2) { tableDownLoaderInfo2.setModel(tableMode_Observer); } }
package DownLoader.ui 中的MyProgressBar类:
package DownLoader.ui; import java.awt.Component; import javax.swing.JProgressBar; import javax.swing.JTable; import javax.swing.table.TableCellRenderer; /** * 进度条渲染器类 * @author Huangyujun * */ public class MyProgressBar extends JProgressBar implements TableCellRenderer{ @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { //获取单元格的值 Float floatValue = Float.valueOf(value.toString()); //设置单元格的值 this.setValue(floatValue.intValue()); return this; } }
package DownLoader.utils 中的ClimbNetPage
package DownLoader.utils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; /** * 爬取网页类--就直接实现下载静态页面的链接就可以啦! * * @author Huangyujun * */ public class ClimbNetPage { private String netUrl; private String localPath; public ClimbNetPage(String netUrl, String localPath) throws IOException { this.netUrl = netUrl; this.localPath = localPath; climb(); } private void climb() throws IOException{ File file = new File(localPath); if(!file.exists()) { file.createNewFile(); } FileOutputStream climbToFile = new FileOutputStream(file); // 网络资源定位 URL url = null; // 远程对象 HttpURLConnection urlConnection = null; // 输入流 InputStream inputStream = null; url = new URL(netUrl); urlConnection = (HttpURLConnection) url.openConnection(); // 设置远程对象的请求属性和一些参数 urlConnection.setRequestProperty("Range", "bytes=0-"); // 发送连接请求 urlConnection.connect(); // 返回码判断连接是否成功 int responCode = urlConnection.getResponseCode(); if (responCode / 100 != 2) { // 返回码错误 System.err.println("连接失败,返回码:" + responCode); } // 获取输入流 inputStream = urlConnection.getInputStream(); byte[] buff = new byte[1024]; int count = 0; while ((count = inputStream.read(buff)) != -1) { climbToFile.write(buff, 0, count); } System.out.println("爬取成功"); inputStream.close(); climbToFile.close(); } }
package DownLoader.utils 中的DownLoader
package DownLoader.utils; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Observable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 下载器类--被观察者 * * @author Huangyujun * */ public class DownLoader extends Observable { // 下载器的状态 private static int status; // 下载器状态的常量 /** 正在下载 */ public static int DOWNLOAD = 0; /** 暂停 */ public static int PAUSE = 1; /** 已完成 */ public static int COMPLETE = 2; /** 取消 */ public static int CANCEL = 3; /** 错误 */ public static int WAIT = 4; // 下载状态错误 private static final String[] strStatus = { "正在下载", "暂停", "已完成", "取消", "等待下载" }; // 下载器的工作是采取多线程下载网络的文件 private String netUrl = null; // 网络路径 private String localPath = null; // 本地路径 private int countOfThread; // 线程数量 private int fileSize; // 文件大小 private int partSize; // 文件分块的大小 private int downloadedBytes; // 已经下载的字节数(算速度、进度) // 线程列表 private List<DownLoaderTask> downLoaderTaskList = new ArrayList<DownLoaderTask>(); // 速度计 private Speeder speeder = null; // 速度 private double speed; // 构造方法 public DownLoader(String netUrl, String localPath, int countOfThread) { this.netUrl = netUrl; this.localPath = localPath; this.countOfThread = countOfThread; File file = new File(netUrl); if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } return; } // 文件大小 fileSize = (int) file.length(); } /** * 下载方法 */ public void download() { try { downloadTest(); } catch (IOException e) { e.printStackTrace(); } } /** * http协议的网络路径下载方法 */ private void httpDownload() { // 同样是从网络下载文件,然后分块交给线程(下载任务) // 网络资源定位 URL url = null; // 远程对象 HttpURLConnection urlConnection = null; try { url = new URL(netUrl); urlConnection = (HttpURLConnection) url.openConnection(); // 设置远程对象的请求属性和一些参数 urlConnection.setRequestProperty("Range", "bytes=0-"); // urlConnection.setRequestProperty("Accept-Encoding", "identity"); // 发送连接请求 urlConnection.connect(); // 返回码判断连接是否成功 int responCode = urlConnection.getResponseCode(); if (responCode / 100 != 2) { // 返回码错误 throw new RuntimeException("连接失败,返回码:" + responCode); } // 连接成功后,获取文件大小,以及分块交给下载任务处理 fileSize = urlConnection.getContentLength(); // 按照线程数分块的大小 partSize = fileSize / countOfThread; // 随机文件流 RandomAccessFile randomFile = new RandomAccessFile(localPath, "rw"); // 多线程 ExecutorService executor = Executors.newFixedThreadPool(countOfThread); for (int i = 0; i < countOfThread; i++) { // 开始位置 int startPositition = i * partSize; // 判断最后一块的大小 if (i == countOfThread - 1 && fileSize % countOfThread != 0) { partSize += fileSize % countOfThread; } //如何下载器状态非下载,则跳出循环 if(getStatus() != DOWNLOAD) { return; } // 自动下载,设置下载器状态为下载 setChanged(DOWNLOAD); // 实例化下载任务 DownLoaderTask downloaderTask = new DownLoaderTask(netUrl, localPath, startPositition, partSize, randomFile, this); // 把下载任务添加到任务列表里 downLoaderTaskList.add(downloaderTask); if(getStatus() == DOWNLOAD) { // 启动线程 executor.execute(downloaderTask); } } // 开启下载后启动速度计线程 speeder = new Speeder(this); speeder.start(); // 开启速度计进程 } catch (IOException e) { e.printStackTrace(); } finally { // 关闭资源 try { urlConnection.disconnect(); } catch (Exception e2) { e2.printStackTrace(); } } } /** * 测试用的下载方法 * @throws IOException */ private void downloadTest() throws IOException { // 按照线程数分块的大小 partSize = fileSize / countOfThread; // 随机文件流 RandomAccessFile randomFile = new RandomAccessFile(localPath, "rw"); // 多线程 ExecutorService executor = Executors.newFixedThreadPool(countOfThread); for (int i = 0; i < countOfThread; i++) { // 开始位置 int startPositition = i * partSize; // 判断最后一块的大小 if (i == countOfThread - 1 && fileSize % countOfThread != 0) { partSize += fileSize % countOfThread; } //如何下载器状态非下载,则跳出循环 if(getStatus() != DOWNLOAD) { return; } // 自动下载,设置下载器状态为下载 setChanged(DOWNLOAD); // 实例化下载任务 DownLoaderTask downloaderTask = new DownLoaderTask(netUrl, localPath, startPositition, partSize, randomFile, this); // 把下载任务添加到任务列表里 downLoaderTaskList.add(downloaderTask); if(getStatus() == DOWNLOAD) { // 启动线程 executor.execute(downloaderTask); } } // 开启下载后启动速度计线程 speeder = new Speeder(this); speeder.start(); // 开启速度计进程 } /** * 暂停 */ public void pause() { setChanged(PAUSE); } /** * 恢复下载 */ public void resume() { this.setChanged(DOWNLOAD); this.download(); } /** * 被观察者最重要的功能是通知状态 * * @param status */ public void setChanged(int status) { this.status = status; this.setChanged(); this.notifyObservers(); } /** * 返回下载状态 * * @return */ public int getStatus() { return this.status; } /** * 返回网络路径 * * @return */ public String getNetUrl() { return this.netUrl; } /** * 返回文件大小 * * @return */ public int getFileSize() { return this.fileSize; } /** * 返回下载状态 * * @return */ public String getStrStaus() { return this.strStatus[this.getStatus()]; } /** * 返回下载器下载的字节数量 * * @return */ public int getDownloadedBytes() { this.downloadedBytes = 0; // 已下载字节数 for (DownLoaderTask task : downLoaderTaskList) { this.downloadedBytes += task.getDownloadedBytes(); } return this.downloadedBytes; } /** * 进度 * * @return */ public double progress() { this.downloadedBytes = this.getDownloadedBytes(); return downloadedBytes / 1.0 / fileSize; } /** * 返回速度计 * * @return */ public Speeder getSpeeder() { return speeder; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + countOfThread; result = prime * result + ((localPath == null) ? 0 : localPath.hashCode()); result = prime * result + ((netUrl == null) ? 0 : netUrl.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DownLoader other = (DownLoader) obj; if (countOfThread != other.countOfThread) return false; if (localPath == null) { if (other.localPath != null) return false; } else if (!localPath.equals(other.localPath)) return false; if (netUrl == null) { if (other.netUrl != null) return false; } else if (!netUrl.equals(other.netUrl)) return false; return true; } }
package DownLoader.utils 中的DownLoaderTask
package DownLoader.utils; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; /** * 下载任务类--线程 * * @author Huangyujun * */ public class DownLoaderTask implements Runnable { // 下载网络文件块 private String netUrl = null; // 网络路径 private String localPath = null; // 本地路径 private int startPosition; // 文件下载起始位置 private int downloadedBytes; // 已经下载的字节数量 private int partSize; // 文件块的大小 private RandomAccessFile randomFile; // 随机文件流 private DownLoader downloader; // 下载器对象属性 private static final int BYTE_SIZE = 1024 * 1024; // 分块的字节大小--1M private static final int KNOWEDURL = 1; // 构造方法 public DownLoaderTask(String netUrl, String localPath, int startPosition, int partSize, RandomAccessFile randomFile, DownLoader downloader) { this.netUrl = netUrl; this.localPath = localPath; this.startPosition = startPosition; this.partSize = partSize; this.randomFile = randomFile; this.downloader = downloader; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "下载开始"); try { downloadTaskTest(); } catch (IOException e) { e.printStackTrace(); } } /** * http协议的下载任务线程方法 */ private void httpdownloadTaskTest() { // 网络资源定位 URL url = null; // 远程对象 HttpURLConnection urlConnection = null; // 输入流 InputStream inputStream = null; try { url = new URL(netUrl); urlConnection = (HttpURLConnection) url.openConnection(); // 设置远程对象的请求属性和一些参数 String range = String.format("bytes=%d-%d", startPosition, startPosition + partSize); urlConnection.setRequestProperty("Range", range); // 发送连接请求 urlConnection.connect(); // 返回码判断连接是否成功 int responCode = urlConnection.getResponseCode(); if (responCode / 100 != 2) { // 返回码错误 throw new RuntimeException("连接失败,返回码:" + responCode); } // 获取输入流 inputStream = urlConnection.getInputStream(); // 开始循环读取--下载字节 < 文件块的大小 和下载器的状态是下载 while (downloadedBytes < partSize && downloader.getStatus() == DownLoader.DOWNLOAD) { byte[] buff = null; // 字节数组 // 为了算速度,分块 if (partSize - downloadedBytes >= BYTE_SIZE) { buff = new byte[BYTE_SIZE]; } else { buff = new byte[partSize - downloadedBytes]; } // 当前的块的读取的字节数 int currReadedBytes = 0; int read = -1; // 一个一个字节读取 while (currReadedBytes < buff.length && (read = inputStream.read()) != -1) { // 读取到字节数字里 buff[currReadedBytes++] = (byte) read; downloadedBytes++; } // 读取到末尾 if (read == -1) { if (currReadedBytes != 0) { // 写入文件 randomFile.seek(startPosition); randomFile.write(buff, 0, currReadedBytes); break; } } if (downloader.getStatus() != DownLoader.DOWNLOAD) { return; } // 写入文件 randomFile.seek(startPosition); randomFile.write(buff); startPosition += buff.length; } if (downloadedBytes == partSize) { System.out.println(Thread.currentThread().getName() + "下载已经完成"); } } catch (IOException e) { e.printStackTrace(); } finally { // 关闭资源 try { inputStream.close(); urlConnection.disconnect(); } catch (Exception e2) { e2.printStackTrace(); } } } /** * 测试用的下载任务线程方法 * * @throws IOException */ private void downloadTaskTest() throws IOException { File file = new File(netUrl); if (!file.exists()) { System.err.println("文件不存在!!!"); return; } InputStream inputStream = new FileInputStream(file); // 开始循环读取--下载字节 < 文件块的大小 和下载器的状态是下载 while (downloadedBytes < partSize && downloader.getStatus() == DownLoader.DOWNLOAD) { byte[] buff = null; // 字节数组 // 为了算速度,分块 if (partSize - downloadedBytes >= BYTE_SIZE) { buff = new byte[BYTE_SIZE]; } else { buff = new byte[partSize - downloadedBytes]; } // 当前的块的读取的字节数 int currReadedBytes = 0; int read = -1; // 一个一个字节读取 while (currReadedBytes < buff.length && (read = inputStream.read()) != -1) { // 读取到字节数字里 buff[currReadedBytes++] = (byte) read; downloadedBytes++; } // 读取到末尾 if (read == -1) { if (currReadedBytes != 0) { // 写入文件 randomFile.seek(startPosition); randomFile.write(buff, 0, currReadedBytes); break; } } if (downloader.getStatus() != DownLoader.DOWNLOAD) { return; } // 写入文件 randomFile.seek(startPosition); randomFile.write(buff); startPosition += buff.length; } if (downloadedBytes == partSize) { System.out.println(Thread.currentThread().getName() + "下载已经完成"); } } /** * 返回下载任务已经下载的字节数量 * * @return */ public int getDownloadedBytes() { return downloadedBytes; } }
package DownLoader.utils 中的ImageUtil
package DownLoader.utils; import java.awt.Image; import java.util.HashMap; import java.util.Map; import javax.swing.ImageIcon; /** * 加载图片工具类,实现加载过的图片可以下次直接获取 * @author Huangyujun * */ public class ImageUtil { //定义Map集合(键:图片路径, 值 图片控件) public static Map<String, ImageIcon> mapImage = new HashMap<String, ImageIcon>(); //获取图片控件 public static ImageIcon getImage(String path) { //判断图片是否加载过 if(mapImage.containsKey(path)) { //加载过的图片,就直接返回图片控件 return mapImage.get(path); } //没有加载过的图片 ImageIcon image = new ImageIcon(path); mapImage.put(path, image); return image; } /** * 缩放图片比例后,返回缩放后的图片控件 * @param image * @param scale */ public static ImageIcon scaleImageIcon(Image image, double scale) { //调用Image控件的方法 int newWidth = (int) (image.getWidth(null) * scale); int newHigt = (int) (image.getHeight(null) * scale); Image scaleImage = image.getScaledInstance(newWidth, newHigt, image.SCALE_DEFAULT); //图标控件里放图片控件 return new ImageIcon(scaleImage); } }
package DownLoader.utils 中的Speeder
package DownLoader.utils; /** * 速度计类 * * @author Huangyujun * */ public class Speeder extends Thread { // 对象属性 private DownLoader downloader; // 速度 private double speed; // 构造方法 public Speeder(DownLoader downloader) { this.downloader = downloader; } @Override public void run() { // 已经下载量 int downloadedBytes = 0; while (true) { while (!Thread.currentThread().isInterrupted()) { if(downloader.getStatus() != DownLoader.DOWNLOAD) { if(downloader.getStatus() == DownLoader.CANCEL) { speed = 0; } break; } // 计算速度 speed = (downloader.getDownloadedBytes() - downloadedBytes) * 2; // 通知速度更新啦 downloader.setChanged(DownLoader.DOWNLOAD); // 更新一下已经下载字节数 downloadedBytes = downloader.getDownloadedBytes(); // 判断是否下载完成 if (downloader.progress() >= 1.0) { downloader.setChanged(DownLoader.COMPLETE); downloader.getSpeeder().threadCancel(); break; } try { // 休眠时间 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * 线程阻断 */ public void threadCancel() { interrupt(); } /** * 返回速度 * @return */ public double getSpeed() { return this.speed; } }
packageDownLoaderTableMode中的DownLoaderTableMode_Observer类
package DownLoaderTableMode; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.Observable; import java.util.Observer; import javax.swing.JOptionPane; import javax.swing.table.AbstractTableModel; import DownLoader.utils.DownLoader; import MyDataBasic.TestDataBasic; /** * 表格模式类--观察者 * * @author Huangyujun * */ public class DownLoaderTableMode_Observer extends AbstractTableModel implements Observer { // 设置表头(列名) private static final String[] tableColumnName = { "链接", "文件大小(MB)", "下载进度", "下载速度", "下载状态" }; // 一张表格有多个下载器,一个下载器有多个下载任务 private List<DownLoader> downloaderList = new ArrayList<DownLoader>(); // 重写表头方法 @Override public String getColumnName(int column) { return tableColumnName[column]; } @Override public int getRowCount() { return downloaderList.size(); } @Override public int getColumnCount() { return tableColumnName.length; } /** * 业务:增加下载器(被观察者) * * @param downloader */ public void addDownloader(DownLoader downloader) { // 判断表格中是否已经有了该下载器 if (downloaderList.contains(downloader)) { JOptionPane.showMessageDialog(null, "已下载,请勿重复操作!"); return; } //判断当前下载器List集合是否有下载器在下载,有则弹出对话框提示用户需要下载完成后再添加 if(isPopDialog()) { JOptionPane.showMessageDialog(null, "请等待当前下载任务完成后添加!"); return; } //设置下载器的状态为下载 downloader.setChanged(DownLoader.DOWNLOAD); // 添加到表格下载器列表里 downloaderList.add(downloader); // 被观察者添加观察者,(观察者与被观察者建立联系) downloader.addObserver(this); // 添加下载器后自动开启下载 downloader.download(); // 通知界面表格模式发生改变 fireTableRowsInserted(getRowCount() - 1, getRowCount() - 1); } /** * 是否弹出提示框 * @return */ private boolean isPopDialog() { //遍历下载器List,判断是否有下载器在下载 for(int i = 0; i < downloaderList.size(); i++) { DownLoader downLoader = downloaderList.get(i); if(downLoader.getStatus() == DownLoader.DOWNLOAD) { return true; } } return false; } /** * 查找含某个字符串的下载器 * @param str */ public void findStrDownLoader(String str) { //含有str下载器的网络路径的下载器List List<DownLoader> strDownLoaderList = new ArrayList<DownLoader>(); for(int i = 0; i < TestDataBasic.netUrl.length; i++) { if(TestDataBasic.netUrl[i].contains(str)) { DownLoader downLoader = TestDataBasic.downLoaderList.get(i); downLoader.setChanged(DownLoader.WAIT); strDownLoaderList.add(downLoader); //添加观察者、被观察的关系 downLoader.addObserver(this); } } //当前下载器List等于找到的下载器List downloaderList = strDownLoaderList; //通知界面表格模式发生改变 fireTableRowsInserted(0, getRowCount() - 1); } /** * 查询后启动下载---线程问题,所以只能先删除"表面的下载器"--再添加下载 * @param row */ public void download(int row) { // 下载器需要删除后再添加 DownLoader downloader = downloaderList.get(row); //清空表格所有的下载器,再添加下载器--因为原来的下载器导致的表格中的下载速度, //原本需要开启线程,而之前是直接返回0,没有涉及到线程,此刻添加下载器,又启动了线程 //这样设计也符合我之前因为单线程计算下载速度计算两个下载器速度时不准确,选择一次只下载一个下载器 clearAllDownloader(); downloader.setChanged(DownLoader.DOWNLOAD); // 添加到表格下载器列表里 downloaderList.add(0, downloader); // 被观察者添加观察者,(观察者与被观察者建立联系) downloader.addObserver(this); // 添加下载器后自动开启下载 downloader.download(); // 通知界面表格模式发生改变 fireTableRowsInserted(0, 0); } /** * 根据下标添加下载器 * @param row */ public void addDownloader(DownLoader downLoader, int row) { //设置下载器的状态为下载 downLoader.setChanged(DownLoader.DOWNLOAD); // 添加到表格下载器列表里 downloaderList.add(downLoader); // 被观察者添加观察者,(观察者与被观察者建立联系) downLoader.addObserver(this); // 添加下载器后自动开启下载 downLoader.download(); // 通知界面表格模式发生改变 fireTableRowsInserted(row, row); } /** * 暂停 * * @param row */ public void pause(int row) { // 判断当前是否有下载器 if (downloaderList.isEmpty()) return; // 判断是哪一个下载器需要暂停 DownLoader pauseDownLoader = downloaderList.get(row); if (pauseDownLoader.getStatus() != DownLoader.PAUSE) { // 修改下载器的状态 pauseDownLoader.pause(); } // 通知界面表格 fireTableDataChanged(); } /** * 恢复下载 * @param row */ public void resume(int row) { // 判断当前是否有下载器 if (downloaderList.isEmpty()) return; // 判断是哪一个下载器需要暂停 DownLoader resumeDownLoader = downloaderList.get(row); if (resumeDownLoader.getStatus() == DownLoader.PAUSE) { // 修改下载器的状态 resumeDownLoader.resume(); } // 通知界面表格 fireTableDataChanged(); } /** * 取消--删除选中的下载器 * @param row */ public void deleteDownloader(int row) { // 判断是哪一个下载器需要删除 DownLoader deleteDownLoader = downloaderList.get(row); if(deleteDownLoader.getStatus() != DownLoader.WAIT) { deleteDownLoader.setChanged(DownLoader.CANCEL); } if(deleteDownLoader.getStatus() == DownLoader.CANCEL) { // deleteDownLoader.getSpeeder().threadCancel(); } downloaderList.remove(deleteDownLoader); // 被观察者删除观察者 deleteDownLoader.deleteObserver(this); // 通知界面表格模式发生改变 fireTableRowsDeleted(row, row); } /** * 清空 */ public void clearAllDownloader() { int len = downloaderList.size(); //不断地调用删除方法 for(int i = 0; i < len; i++) { DownLoader deleteDownLoader = downloaderList.get(i); deleteDownLoader.setChanged(DownLoader.CANCEL); // 被观察者删除观察者 deleteDownLoader.deleteObserver(this); } downloaderList.clear(); // 通知界面表格模式发生改变 fireTableRowsDeleted(0, len); } @Override public Object getValueAt(int rowIndex, int columnIndex) { // 判断表格是否有下载器 if (downloaderList.isEmpty()) return null; // 判断是哪一个下载器更新了 DownLoader currDownloader = downloaderList.get(rowIndex); // 小数格式 DecimalFormat format = new DecimalFormat("#.#"); // 通过switch结构获取属性 switch (columnIndex) { case 0: // 链接 return currDownloader.getNetUrl(); case 1:// 文件大小 int fileSize = currDownloader.getFileSize(); // 文件大小为-1时的处理为-- if (fileSize == -1) { return "--"; } return format.format(fileSize / 1024 / 1024.0) + "MB"; case 2:// 下载进度=下载器已经下载的字节数/总文件大小 return new Float(currDownloader.progress() * 100); case 3:// 下载速度 if(currDownloader.getStatus() == DownLoader.WAIT) { return "0 MB/s"; }else { return format.format(currDownloader.getSpeeder().getSpeed() / 1024 / 1024.0) + "MB/s"; } case 4:// 下载状态 return currDownloader.getStrStaus(); } return null; } @Override public void update(Observable o, Object arg) { // 被观察者(下载器)更新状态后,需要通知界面层 // 判断表格是否有下载器 if (downloaderList.isEmpty()) return; // 判断是哪一个下载器更新了 int newRow = downloaderList.indexOf(o); // 判断是否选中下载器 if (newRow == -1) return; // 通知界面层,表格行数据更新 fireTableRowsUpdated(newRow, newRow); } }
package MyDataBasic 中的类TestDataBasic
package MyDataBasic; import java.util.ArrayList; import java.util.List; import DownLoader.utils.DownLoader; public class TestDataBasic { public static List<DownLoader> downLoaderList = null; public static String[] netUrl = { "D:\\EclispeProjects\\MyDownLoader\\src\\sounds\\music.mp4", "D:\\EclispeProjects\\MyDownLoader\\src\\sounds\\car.mp4", "D:\\EclispeProjects\\MyDownLoader\\src\\sounds\\car2.mp4", "D:\\EclispeProjects\\MyDownLoader\\src\\sounds\\car3.mp4", "D:\\EclispeProjects\\MyDownLoader\\src\\sounds\\stop.mp4", "D:\\EclispeProjects\\MyDownLoader\\src\\sounds\\tiyu.mp4" }; public static String[] localPath = new String[netUrl.length]; static { downLoaderList = new ArrayList<DownLoader>(); for(int i = 0; i < netUrl.length; i++) { localPath[i] = "D:\\test\\" + netUrl[i].substring(netUrl[i].lastIndexOf("\\") + 1); //创建和添加下载器对象 DownLoader downloader = new DownLoader(netUrl[i], localPath[i], 5); downLoaderList.add(downloader); } } }
另外整理一下出现的bug:由于bug的出现以及为了掩盖bug,里边我加入了一些掩盖bug代码,我整理一下bug,小伙伴可以不用我的方式改bug,因为我的方式更像一种掩盖,通过加入判断条件,把bug掩盖掉了(bug整理放到我自己的评论里)