自己寫的一個簡單的迅雷下載支持斷點續傳

當我學習了網絡線程,就自己仿照迅雷下載寫了一個下載器,支持斷點續傳

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

	}
}

學習了網絡線程,自己寫一個程序,來加深理解








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章