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

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

我用的是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
    评论
### 回答1: 迅雷是一款流行的下载工具,它采用了多种机制来加速下载速度和提高下载成功率。下面是一些可能的迅雷下载文件的机制: 1. 多线程下载迅雷会利用多个线程同时下载一个文件,从而提高下载速度。每个线程负责下载文件的不同部分,当一个线程遇到问题时,其他线程可以继续下载,从而减少下载中断的概率。 2. P2P下载迅雷支持点对点(P2P)下载,即通过与其他用户共享下载文件的部分,从而加快下载速度。当用户下载一个文件时,迅雷会查找其他正在下载或已经下载该文件的用户,以从他们那里获取该文件的部分。 3. 自动镜像选择:当用户下载一个文件时,迅雷会自动选择最快的下载源,即镜像站点。这样可以避免用户选择下载源时产生的不便和错误。 4. 文件预读:迅雷会在下载文件时预读未下载的部分,从而加快下载速度。在下载期间,迅雷还会自动对下载的文件进行校验,以确保文件的完整性和正确性。 总之,迅雷下载文件的机制包括多线程下载、P2P下载、自动镜像选择和文件预读等多个方面,这些机制共同作用来提高下载速度和下载成功率。 ### 回答2: 迅雷是一种常用的下载工具,其下载文件的机制如下: 当用户在迅雷中输入下载链接或搜索下载内容,迅雷会连接至服务器检索相关文件信息。若服务器支持迅雷协议,用户将被迅雷识别为合法用户。 迅雷下载文件的机制包含了两种方式:HTTP/FTP下载和P2P下载。 在HTTP/FTP下载方式中,用户请求下载文件时,迅雷会和服务器进行直接的HTTP/FTP通信。迅雷通过多线程技术将下载任务分解为多个子任务,同时使用多个线程分别下载一个文件的不同部分。这样可以提高下载速度并增加下载的稳定性。迅雷还会检测下载连接,并自动断点续传以避免因网络问题或其他原因导致的下载中断。 在P2P下载方式中,用户将下载链接输入到迅雷中后,迅雷会根据用户的选择自动寻找与该文件相关的种子文件。通过种子文件,迅雷能够在全球范围内寻找其他正在下载或已经下载完成该文件的用户,建立连接并从这些用户处获取文件的分块数据。迅雷通过多线程和多点下载技术,同时从多个用户处下载不同的文件分块,并将这些分块即时组合成完整的文件。这样可以提高下载速度并减轻服务器负载。 无论是HTTP/FTP下载还是P2P下载迅雷都会自动优化下载顺序以提高下载效率,并根据用户设定的参数进行下载管理,如同时下载任务数、下载速度限制等。 总之,迅雷通过多线程断点续传、多点下载和全球用户共享资源等技术,提供了快速、稳定的下载机制,使用户能够高效地下载和管理各种文件。 ### 回答3: 迅雷是一款常用的下载工具,其下载文件的机制如下: 首先,用户通过复制和粘贴或手动输入下载链接,将需要下载的文件链接添加到迅雷软件中。 然后,迅雷会解析该下载链接,并获取文件的相关信息,包括文件大小、文件类型等。 接着,迅雷会根据用户选择的下载方式,使用多通道技术来同时从不同的源服务器上获取文件的数据。迅雷会先从文件资源最丰富的服务器开始下载,确保下载速度更快。 在下载过程中,迅雷会根据服务器的响应情况动态调整下载通道和线程数,以达到最快的下载速度。同时,迅雷支持断点续传功能,即使下载过程中出现网络中断或软件异常退出,下次重新开始下载时可以从上次下载的位置继续,不需要重新下载整个文件。 为了提高下载速度和下载成功率,迅雷还采用了一些优化措施。例如,它会自动选择最佳的下载源服务器、自动加速下载支持下载加速插件等。 最后,在文件下载完成后,迅雷会对下载的文件进行校验,确保文件的完整性和准确性。 总的来说,迅雷通过多通道技术、断点续传、动态调整下载通道和线程数等方式来提高下载速度和成功率;同时,它还支持自动加速、下载加速插件等功能来满足用户的需求。迅雷下载机制使得用户能够更快速、更方便地下载所需的文件。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值