当我学习了网络线程,就自己仿照迅雷下载写了一个下载器,支持断点续传
我用的是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> threadInfos = new HashMap>();
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 downLoadTasks = new ArrayList();
private long allThreaddownLoadedSize;
private String key;
private Map> map;
private int threadSize;
private String urlString;
private String savePath;
private File downLoadFile;
private List threads=new ArrayList();
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>();
}
}
public long getAllThreadDownLoadedSize(String key) {
allThreaddownLoadedSize = 0;
if (map != null && map.size() > 0) {
List 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 list = new ArrayList();
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 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> threadInfos = getDownLoadedThreadInfoMapFromTmpFile();
List list = new ArrayList();
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> getDownLoadedThreadInfoMapFromTmpFile() {
Map> 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>) 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;
}
}