当我学习了网络线程,就自己仿照迅雷下载写了一个下载器,支持断点续传
我用的是SWT插件做的界面
界面
package com.yc.xunlei; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.ProgressBar; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; public class Xunlei { protected Shell shell; private Text txt; private Combo combo; private long sum; private ProgressBar progressBar; private File downLoadFile; private Text text; private Map<String, List<ThreadInfo>> threadInfos = new HashMap<String, List<ThreadInfo>>(); private Label label_2; private String key; DownLoadUtils dlu; /** * Launch the application. * * @param args */ public static void main(String[] args) { try { Xunlei window = new Xunlei(); window.open(); } catch (Exception e) { e.printStackTrace(); } } /** * Open the window. */ public void open() { Display display = Display.getDefault(); createContents(); shell.open(); shell.layout(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } } /** * Create contents of the window. */ protected void createContents() { shell = new Shell(); shell.setSize(610, 468); shell.setText("\u8FC5\u96F7\u4E0B\u8F7D"); Label lblUrl = new Label(shell, SWT.NONE); lblUrl.setBounds(26, 36, 40, 15); lblUrl.setText("url:"); txt = new Text(shell, SWT.BORDER); txt .setText("http://dlsw.baidu.com/sw-search-sp/soft/3a/12350/QQ_v7.3.15056.0_setup.1435111953.exe"); txt.setBounds(72, 33, 520, 18); Button button = new Button(shell, SWT.NONE); button.setBounds(127, 201, 72, 22); button.setText("\u4E0B\u8F7D"); Button button_1 = new Button(shell, SWT.NONE); button_1.setBounds(236, 201, 72, 22); button_1.setText("\u6682\u505C"); progressBar = new ProgressBar(shell, SWT.NONE); progressBar.setBounds(72, 245, 461, 17); Label label = new Label(shell, SWT.NONE); label.setBounds(26, 83, 42, 25); label.setText("\u7EBF\u7A0B\u6570:"); combo = new Combo(shell, SWT.NONE); combo.setItems(new String[] { "5", "6", "7", "8", "9", "10" }); combo.setBounds(72, 83, 87, 20); combo.select(0); Label label_1 = new Label(shell, SWT.NONE); label_1.setBounds(26, 141, 54, 18); label_1.setText("\u4FDD\u5B58\u4F4D\u7F6E:"); text = new Text(shell, SWT.BORDER); text.setBounds(89, 141, 296, 18); text.setText(System.getProperty("user.home")); label_2 = new Label(shell, SWT.NONE); label_2.setBounds(127, 281, 342, 31); // 暂停的方法 button_1.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (dlu != null) { dlu.stop(); } } }); /** * a. 创建要下载的文件到本地磁盘 b. 设置界面上progressbar的总长度 c. 再开始下载 * d.修改System.out.println("已经下载了:"+ sum+"个字节");为 progressbar的设置 */ button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { sum = 0; // 每次点ji开始时,要将sum赋初值为0 String urlString = txt.getText().trim(); int threadSize = Integer.parseInt(combo.getText()); String savePath = text.getText(); try { dlu = new DownLoadUtils(threadSize, urlString, savePath); downLoadFile = dlu.getDownLoadFile(); key = threadSize + "_" + urlString + "_" + downLoadFile.getAbsolutePath(); // 设置界面上progressbar的总长度 progressBar.setMaximum((int) downLoadFile.length()); long allThreadDownLoadedSize = dlu .getAllThreadDownLoadedSize(key); label_2.setText("总长度:" + (int) downLoadFile.length() + "/已下载的长度" + allThreadDownLoadedSize); sum += allThreadDownLoadedSize; dlu.downLoad(downLoadFile, urlString, threadSize, new OnSizeChangeListener() { public void onSizeChange(long downLoadSize) { sum += downLoadSize; Display.getDefault().asyncExec( new Runnable() { @Override public void run() { progressBar .setSelection((int) sum); label_2 .setText("总长度:" + (int) downLoadFile .length() + "/已下载的长度" + sum); } }); if (sum >= downLoadFile.length()) { dlu.stop(); Display.getDefault().asyncExec( new Runnable() { @Override public void run() { MessageBox mb = new MessageBox( shell, SWT.NO); mb.setText("下载完毕"); mb.setMessage("OK"); mb.open(); } }); } } }); } catch (IOException e1) { e1.printStackTrace(); } } }); } }
实体类 bean(ThreadInfo)
package com.yc.xunlei; import java.io.Serializable; public class ThreadInfo implements Serializable { private static final long serialVersionUID = -8664947024042932015L; private int threadId; private long downLoadSize; public int getThreadId() { return threadId; } public void setThreadId(int threadId) { this.threadId = threadId; } public long getDownLoadSize() { return downLoadSize; } public void setDownLoadSize(long downLoadSize) { this.downLoadSize = downLoadSize; } public ThreadInfo(int threadId, long downLoadSize) { super(); this.threadId = threadId; this.downLoadSize = downLoadSize; } public ThreadInfo() { super(); } @Override public String toString() { return threadId + "\t" + downLoadSize; } }
回调接口. 用来通知主线程下载的数据量...
package com.yc.xunlei; /** * 回调接口. 用来通知主线程下载的数据量... * @author Administrator * */ public interface OnSizeChangeListener { public void onSizeChange( long downLoadSize ); }
下载任务类类
package com.yc.xunlei; import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; public class DownLoadTask implements Runnable { private File downLoadFile; private String urlString; private long startPosition; private long endPosition; private int threadId; private OnSizeChangeListener onSizeChangeListener ; private boolean flag=true; private long downLoadedSize=0; private long downLoadSizePerThread; public long getDownLoadedSize() { return downLoadedSize; } public int getThreadId() { return threadId; } public void stop( ){ this.flag=false; try { this.finalize(); } catch (Throwable e) { e.printStackTrace(); } } public DownLoadTask(File downLoadFile, String urlString, long startPosition, long endPosition, int threadId, OnSizeChangeListener onSizeChangeListener, long downLoadSizePerThread ) { this.downLoadFile = downLoadFile; this.urlString = urlString; this.startPosition = startPosition; this.endPosition = endPosition; this.threadId = threadId; this.onSizeChangeListener= onSizeChangeListener ; this.downLoadSizePerThread=downLoadSizePerThread; } public void run() { downLoadedSize= startPosition- threadId*downLoadSizePerThread; try { URL url = new URL(urlString); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); // 请求头 con.setConnectTimeout(5 * 1000); // 请求过期的时间 con.setRequestProperty("Connection", "Keep-alive"); // TODO:发出协议,指定Range con.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition); RandomAccessFile raf = new RandomAccessFile(downLoadFile, "rw"); // TODO: raf不能从第0个字节写入,而必须从 startPosition位置写入 ,问题来了,如何控制raf从指定位置写入呢? raf.seek(startPosition); InputStream iis = con.getInputStream(); byte[] bs = new byte[1024]; int length = -1; while ((length = iis.read(bs, 0, bs.length)) != -1) { raf.write(bs, 0, length); if( this.onSizeChangeListener!=null ){ onSizeChangeListener.onSizeChange( length ); } this.downLoadedSize+= length; //标量,用于控制线程的暂停 if( !flag){ break; } } iis.close(); con.disconnect(); raf.close(); System.out.println(threadId + "号线程下载完成,范围" + startPosition + "至" + endPosition); } catch (Exception e) { e.printStackTrace(); } } }
下载 Util类
package com.yc.xunlei; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class DownLoadUtils { private List<DownLoadTask> downLoadTasks = new ArrayList<DownLoadTask>(); private long allThreaddownLoadedSize; private String key; private Map<String, List<ThreadInfo>> map; private int threadSize; private String urlString; private String savePath; private File downLoadFile; private List<Thread> threads=new ArrayList<Thread>(); public File getDownLoadFile() { return this.downLoadFile; } public DownLoadUtils(int threadSize, String urlString, String savePath) throws IOException { this.threadSize = threadSize; this.urlString = urlString; this.savePath = savePath; key = threadSize + "_" + urlString + "_" + savePath + File.separator + getDownLoadFileName(urlString); downLoadFile = createDownLoadFile(urlString, savePath); map=getDownLoadedThreadInfoMapFromTmpFile(); System.out.println("读取到的数据:"+ map ); if( map==null){ map=new HashMap< String, List<ThreadInfo>>(); } } public long getAllThreadDownLoadedSize(String key) { allThreaddownLoadedSize = 0; if (map != null && map.size() > 0) { List<ThreadInfo> list = map.get(key); for (ThreadInfo ti : list) { allThreaddownLoadedSize += ti.getDownLoadSize(); } } return allThreaddownLoadedSize; } public void stop() { if (downLoadTasks != null && downLoadTasks.size() > 0) { for (int i=0;i<threads.size();i++) { downLoadTasks.get(i).stop(); Thread t=threads.get(i); t=null; } } if (isDownLoadFinish()) { map.remove(key); } recordDownLoadThreadInfo(); } private void recordDownLoadThreadInfo() { ObjectOutputStream oos = null; try { List<ThreadInfo> list = new ArrayList<ThreadInfo>(); for (DownLoadTask dlt : downLoadTasks) { // 在DownLoadTask中增加一个属性,表示这个线程下载的线据量, 累加 // 当暂停时,在这里,调用 getxxx方法得到空上线程下载的量. // 操作磁盘记录 ThreadInfo ti = new ThreadInfo(); ti.setThreadId(dlt.getThreadId()); ti.setDownLoadSize(dlt.getDownLoadedSize()); list.add(ti); } map.put(key, list); System.out.println( "保存的数据:"+map ); FileOutputStream fos = new FileOutputStream(new File(System .getProperty("user.home"), "data.tmp")); oos = new ObjectOutputStream(fos); oos.writeObject(map); oos.flush(); } catch (Exception e1) { e1.printStackTrace(); } finally { try { if (oos != null) { oos.close(); } } catch (IOException e1) { e1.printStackTrace(); } } } private boolean isDownLoadFinish() { // 计算当前下载的总长度 long downSize = 0; for (DownLoadTask dlt : downLoadTasks) { downSize += dlt.getDownLoadedSize(); } // 文件总长度 long totalLength = this.downLoadFile.length(); if (downSize >= totalLength) { return true; } else { return false; } } /** * 多线程下载的实现 * * @param downLoadFile * @param urlString * @param threadSize * @throws IOException */ public List<DownLoadTask> downLoad(File downLoadFile, String urlString, int threadSize, OnSizeChangeListener onSizeChangeListener) throws IOException { long startPosition = 0; // 当前线程的起始位置 long endPosition = 0; // 当前线程的结束 // 获取每个线程要下载的长度 long downLoadSizePerThread = getDownLoadSizePerThread(downLoadFile .length(), threadSize); // TODO: 1. 拼接map的键 2. 到 磁盘上找是否有map,map中是否有这个键, // 3. 有则取出值 4. 循环来计算这个起始位置 key = threadSize + "_" + urlString + "_" + downLoadFile.getAbsolutePath(); Map<String, List<ThreadInfo>> threadInfos = getDownLoadedThreadInfoMapFromTmpFile(); List<ThreadInfo> list = new ArrayList<ThreadInfo>(); if (threadInfos != null) { list = threadInfos.get(key); } for (int i = 0; i < threadSize; i++) { if (list.size()>0 && list.get(i) != null) { // 起始位置 startPosition = i * downLoadSizePerThread + list.get(i).getDownLoadSize(); allThreaddownLoadedSize += list.get(i).getDownLoadSize(); } else { // 起始位置 startPosition = i * downLoadSizePerThread; } // 终点位置 endPosition = (i + 1) * downLoadSizePerThread - 1; // TODO:这个地方必须取得所有的DownLoadTask的实例, 返回给主界面,再调用 // DownLoadTask中的某个方法,来设置标量. downLoadTasks.add(new DownLoadTask(downLoadFile, urlString, startPosition, endPosition, i, onSizeChangeListener, downLoadSizePerThread )); } if (allThreaddownLoadedSize < downLoadFile.length()) { for (int i = 0; i < threadSize; i++) { Thread t=new Thread(downLoadTasks.get(i) ); threads.add( t ); t.start(); } } return downLoadTasks; } /** * 读取临时文件中存的已经下载的线程的信息 * * @return */ private Map<String, List<ThreadInfo>> getDownLoadedThreadInfoMapFromTmpFile() { Map<String, List<ThreadInfo>> threadInfos = null; ObjectInputStream ois = null; FileInputStream fis = null; try { File f = new File(System.getProperty("user.home"), "data.tmp"); if (!f.exists()) { return null; } fis = new FileInputStream(f); ois = new ObjectInputStream(fis); threadInfos = (Map<String, List<ThreadInfo>>) ois.readObject(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (ois != null) ois.close(); } catch (IOException e) { e.printStackTrace(); } try { if (fis != null) fis.close(); } catch (IOException e) { e.printStackTrace(); } } return threadInfos; } /** * 计算每个线程要下载的长度 * * @param fileLength * @param threadSize * @return */ public long getDownLoadSizePerThread(long fileLength, int threadSize) { long downLoadSizePerThread = 0; downLoadSizePerThread = fileLength % threadSize == 0 ? fileLength / threadSize : fileLength / threadSize + 1; return downLoadSizePerThread; } /** * 将指定的urlString下的文件下载到 savePath路径下 * * @param urlString * @param savePath * @return 保存的文件对象 * @throws IOException */ public File createDownLoadFile(String urlString, String savePath) throws IOException { // 1. 取出要下载的文件长度,使用 "HEAD"请求头 long length = getDownLoadFileLength(urlString); // 2. 从urlString中取出文件名 String fileName = getDownLoadFileName(urlString); // 3. 创建文件到moren路径或指定路径下 File downLoadFile = createFile(savePath, fileName, length); return downLoadFile; } /** * 取出要下载的文件的长度 * * @param urlString * : 要下载的文件的地址 * @return length: 文件长度 字节长度 * @throws IOException */ public long getDownLoadFileLength(String urlString) throws IOException { long length = -1; // 1.取要下载的文件 长度 URL url = new URL(urlString); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("HEAD"); // 请求头 con.setConnectTimeout(5 * 1000); // 请求过期的时间 con.connect(); length = con.getContentLength(); return length; } /** * 根据url获取要下载的文件名 * * @param urlString * @return 要下载的文件名 * @throws MalformedURLException * @throws MalformedURLException */ public String getDownLoadFileName(String urlString) throws MalformedURLException { if (urlString == null || "".equals(urlString)) { throw new IllegalArgumentException("文件名不能为空"); } URL url = new URL(urlString); String file = url.getFile(); String fileName = file.substring(file.lastIndexOf("/") + 1); return fileName; } /** * 根据目录名,文件名,长度,创建一个文件到指定位置 * * @param directory * : null "" fffff:\ * @param fileName * @param length * @return 创建的文件对象 * @throws IOException */ public File createFile(String directory, String fileName, long length) throws IOException { String directoryPath = null; if (directory != null && !"".equals(directory) && new File(directory).exists()) { directoryPath = directory; } else { directoryPath = System.getProperty("user.home"); } if (fileName == null || "".equals(fileName)) { throw new IllegalArgumentException("文件名不存在"); } if (length <= 0) { throw new IllegalArgumentException("文件大小不能小于0字节"); } File f = new File(directoryPath, fileName); // TODO:要判断这个文件是否存在,没有则创建,有,表示有两种情况: 1. 已经 下载完成, 2. 需要断点续传.. if (f.exists()) { return f; } RandomAccessFile raf = new RandomAccessFile(f, "rw"); raf.setLength(length); return f; } }
学习了网络线程,自己写一个程序,来加深理解
版权声明:本文为博主原创文章,未经博主允许不得转载。
时间: 2025-01-17 01:40:44