自己写的一个简单的迅雷下载支持断点续传

来源:互联网 发布:合肥淘宝摄影机构 编辑:程序博客网 时间:2024/05/16 15:39

当我学习了网络线程,就自己仿照迅雷下载写了一个下载器,支持断点续传

我用的是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() {@Overridepublic void widgetSelected(SelectionEvent e) {if (dlu != null) {dlu.stop();}}});/** * a. 创建要下载的文件到本地磁盘 b. 设置界面上progressbar的总长度 c. 再开始下载 * d.修改System.out.println("已经下载了:"+ sum+"个字节");为 progressbar的设置 */button.addSelectionListener(new SelectionAdapter() {@Overridepublic void widgetSelected(SelectionEvent e) {sum = 0; // 每次点ji开始时,要将sum赋初值为0String 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() {@Overridepublic void run() {progressBar.setSelection((int) sum);label_2.setText("总长度:"+ (int) downLoadFile.length()+ "/已下载的长度"+ sum);}});if (sum >= downLoadFile.length()) {dlu.stop();Display.getDefault().asyncExec(new Runnable() {@Overridepublic 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();}@Overridepublic 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:发出协议,指定Rangecon.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;}}

学习了网络线程,自己写一个程序,来加深理解








1 0