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

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

我用的是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;

	}
}

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








  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值