`
wuhongyu
  • 浏览: 407358 次
  • 性别: Icon_minigender_1
  • 来自: 哈尔滨
社区版块
存档分类
最新评论

多线程断点续传

阅读更多

    以前看过一个前辈写的断点续传的文章,记得当时没看懂,就扔那了。昨天翻了出来,自己也仿照写了一个,不过感觉没人家写的好,有点乱,希望大家能来批评、指正,给点意见!

 

    功能很简单,就是启动多个线程分别从给定的地址下载数据,用RandomAccessFile写到目标文件。实现思路是:

    1、获得连接的长度(即要下载的文件大小),除以设定的线程数,即得到每个线程要下载的大小。

    2、记录临时文件,文件中记录每个线程的编号(id),该线程要下载的起始位置、终止位置和当前位置(当前位置在首次下载时与起始位置相同)。

    3、启动具体执行下载任务的线程,并等待其结束。

    4、下载完成,删除临时文件。

 

代码如下:

主线程及测试的main方法

package com.why.download.test;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 
 * @author why
 *
 */
public class DownLoad {
	//文件目录、文件名
	public String fileDir = "E:/MyDownLoad";
	public String fileName;
	//超时重连时间
	public long reconnectTime = 5;
	//线程数
	private int poolSize = 5;
	//每个线程的缓冲区大小
	public int bufferSize = 1024;
	//url地址
	private String urlLocation = null;
	
	public DownLoad(){}
	public DownLoad(String url){
		this.urlLocation = url;
	}
	public void downLoad(){
		if(this.urlLocation == null || "".equals(this.urlLocation))return;
		downLoad(this.urlLocation);
	}
	public void downLoad(String urlLocation){
		File file = null;
		File tempFile = null;
		CountDownLatch latch;
		URL url = null;
		ExecutorService pool = Executors.newCachedThreadPool();
		long contentLength = 0;
		long threadLength = 0;
		try {
			//如果未指定名称,则从url中获得下载的文件格式与名字
			if(fileName == null || "".equals(fileName)){
				this.fileName = urlLocation.substring(urlLocation.lastIndexOf("/") + 1,
						urlLocation.lastIndexOf("?") > 0 ? urlLocation.lastIndexOf("?")
								: urlLocation.length());
				if ("".equalsIgnoreCase(this.fileName)) {
					this.fileName = UUID.randomUUID().toString();
				}
			}
			new File(fileDir).mkdirs();
			file = new File(fileDir + File.separator + fileName);
			tempFile = new File(fileDir + File.separator + fileName + "_temp");
			
			url = new URL(urlLocation);
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			setHeader(conn);
			//得到content的长度
			contentLength = conn.getContentLength();
			
			System.out.println("total length=" + contentLength);
			
			//把context分为poolSize段,计算每段的长度。
//			threadLength = contentLength / this.poolSize;
			BigDecimal b1 = new BigDecimal(Double.toString(contentLength));
			BigDecimal b2 = new BigDecimal(Double.toString(this.poolSize));
			threadLength = b1.divide(b2, 0, BigDecimal.ROUND_HALF_UP).longValue();
			
			if(file.exists() && tempFile.exists()){
				//如果文件已存在,根据临时文件中记载的线程数量,继续上次的任务
				latch = new CountDownLatch((int)tempFile.length()/28);
				for(int i=0;i<tempFile.length()/28;i++){
					pool.submit(new DownLoadTask(file, tempFile, url, i+1,latch,reconnectTime,bufferSize));
				}
			}else{
				//如果下载的目标文件不存在,则创建新文件
				latch = new CountDownLatch(poolSize);
				file.createNewFile();
				tempFile.createNewFile();
				DataOutputStream os = new DataOutputStream(new FileOutputStream(tempFile));
				for(int i=0;i<this.poolSize;i++){
					os.writeInt(i+1);
					os.writeLong(i*threadLength);
					if(i==this.poolSize-1){//最后一个线程的结束位置应为文件末端
						os.writeLong(contentLength);
					}else{
						os.writeLong((i+1)*threadLength);
					}
					os.writeLong(i*threadLength);
					pool.submit(new DownLoadTask(file, tempFile, url, i+1,latch,reconnectTime,bufferSize));
				}
				os.close();
			}
			//等待下载任务完成
			latch.await();
			//删除临时文件
			tempFile.delete();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally{
			pool.shutdown(); 
		}
	}
	
	private void setHeader(URLConnection conn) {
		conn.setRequestProperty("User-Agent",
						"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/8.04 (hardy) Firefox/3.0.3");
		conn.setRequestProperty("Accept-Language", "en-us,en;q=0.7,zh-cn;q=0.3");
		conn.setRequestProperty("Accept-Encoding", "aa");
		conn.setRequestProperty("Accept-Charset","ISO-8859-1,utf-8;q=0.7,*;q=0.7");
		conn.setRequestProperty("Keep-Alive", "300");
		conn.setRequestProperty("Connection", "keep-alive");
		conn.setRequestProperty("If-Modified-Since", "Fri, 02 Jan 2009 17:00:05 GMT");
		conn.setRequestProperty("If-None-Match", "\"1261d8-4290-df64d224\"");
		conn.setRequestProperty("Cache-Control", "max-age=0");
		conn.setRequestProperty("Referer","http://www.skycn.com/soft/14857.html");
	}

	
	public String getFileDir() {
		return fileDir;
	}
	public void setFileDir(String fileDir) {
		this.fileDir = fileDir;
	}
	public String getFileName() {
		return fileName;
	}
	public void setFileName(String fileName) {
		this.fileName = fileName;
	}
	public long getReconnectTime() {
		return reconnectTime;
	}
	public void setReconnectTime(long reconnectTime) {
		this.reconnectTime = reconnectTime;
	}
	public int getPoolSize() {
		return poolSize;
	}
	public void setPoolSize(int poolSize) {
		this.poolSize = poolSize;
	}
	public int getBufferSize() {
		return bufferSize;
	}
	public void setBufferSize(int bufferSize) {
		this.bufferSize = bufferSize;
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		DownLoad dl = new DownLoad();
		dl.setFileDir("E:/MyDownLoad/music/");
		dl.setFileName("大笑江湖.mp3");
		dl.setPoolSize(20);
		long beginTime = System.currentTimeMillis();
		dl.downLoad("http://mh.163k.com/UploadFile/video/2010/12-13/201012131213448942190.mp3");
		long endTime = System.currentTimeMillis();
		BigDecimal b1 = new BigDecimal(endTime - beginTime);
		BigDecimal b2 = new BigDecimal(1000);
		double cost = b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP).doubleValue();
		System.out.println("Time cost:" + cost + "s");
	}

}
 

 

执行下载任务的线程:

package com.why.download.test;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * 
 * @author why
 *
 */
public class DownLoadTask implements Callable<String>{
	//超时重连时间
	private long reconnectTime = 5;
	//缓冲区大小
	private int bufferSize = 1024;
	
	private CountDownLatch latch;
	private RandomAccessFile file = null;
	private RandomAccessFile tempFile = null;
	private URL url = null;
	private int id;
	private long startPosition;
	private long endPosition;
	private long currentPosition ;
	
	public DownLoadTask(File file,File tempFile,URL url,int id,CountDownLatch latch,long reconnectTime,int bufferSize){
		try {
			this.file = new RandomAccessFile(file, "rw");
			this.tempFile = new RandomAccessFile(tempFile, "rw");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		this.url = url;
		this.id = id;
		this.latch = latch;
	}
	
	public String call(){
		
		try {
			tempFile.seek((id-1)*28);
			tempFile.readInt();
			this.startPosition = tempFile.readLong();
			this.endPosition = tempFile.readLong();
			this.currentPosition = tempFile.readLong();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		System.out.println("Thread " + id + " begin!");
		
		HttpURLConnection conn = null;
		InputStream inputStream = null;

		while(true){
			try {
				tempFile.seek(id*28 - 8);
				// 打开URLConnection
				conn = (HttpURLConnection) this.url.openConnection();
				setHeader(conn);
				// 设置连接超时时间为10000ms
				conn.setConnectTimeout(10000);
				// 设置读取数据超时时间为10000ms
				conn.setReadTimeout(10000);

				if (currentPosition < endPosition) {
					// 设置下载数据的起止区间
					conn.setRequestProperty("Range", "bytes=" + currentPosition + "-" + endPosition);
					
					System.out.println("Thread " + id + " startPosition=" + startPosition 
							+ ",endPosition=" + endPosition + ",currentPosition=" + currentPosition);

					file.seek(currentPosition);

					// 判断http status是否为HTTP/1.1 206 Partial Content或者200 OK
					// 如果不是以上两种状态,把status改为STATUS_HTTPSTATUS_ERROR
					if (conn.getResponseCode() != HttpURLConnection.HTTP_OK
							&& conn.getResponseCode() != HttpURLConnection.HTTP_PARTIAL) {
						System.out.println("Thread " + id + ": code = " + conn.getResponseCode() + ", status = " + conn.getResponseMessage());
						file.close();
						conn.disconnect();
						System.out.println("Thread " + id + " finished.");
						break;
					}

					inputStream = conn.getInputStream();
					int len = 0;
					byte[] b = new byte[bufferSize];
					while ((len = inputStream.read(b)) != -1) {
						file.write(b, 0, len);

						currentPosition += len;
						// set tempFile now position
						tempFile.seek(id*28 - 8);
						tempFile.writeLong(currentPosition);
					}

					file.close();
					tempFile.close();
					inputStream.close();
					conn.disconnect();
				}

				System.out.println("Thread " + id + " finished.");
				break;
			} catch (IOException e) {
				try {
					TimeUnit.SECONDS.sleep(getReconnectTime());
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				continue;
			}
		}
		latch.countDown();
		return "finish";
	}
	
	private void setHeader(URLConnection conn) {
		conn.setRequestProperty("User-Agent",
						"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/8.04 (hardy) Firefox/3.0.3");
		conn.setRequestProperty("Accept-Language", "en-us,en;q=0.7,zh-cn;q=0.3");
		conn.setRequestProperty("Accept-Encoding", "aa");
		conn.setRequestProperty("Accept-Charset","ISO-8859-1,utf-8;q=0.7,*;q=0.7");
		conn.setRequestProperty("Keep-Alive", "300");
		conn.setRequestProperty("Connection", "keep-alive");
		conn.setRequestProperty("If-Modified-Since", "Fri, 02 Jan 2009 17:00:05 GMT");
		conn.setRequestProperty("If-None-Match", "\"1261d8-4290-df64d224\"");
		conn.setRequestProperty("Cache-Control", "max-age=0");
		conn.setRequestProperty("Referer","http://www.skycn.com/soft/14857.html");
	}

	public long getReconnectTime() {
		return reconnectTime;
	}

	public void setReconnectTime(long reconnectTime) {
		this.reconnectTime = reconnectTime;
	}

	public int getBufferSize() {
		return bufferSize;
	}

	public void setBufferSize(int bufferSize) {
		this.bufferSize = bufferSize;
	}

	
}
 

    最近一直在测试,写文档,好久没敲代码了。手有点痒,写着玩的,代码写的不是很工整,纯属娱乐!

 

 

分享到:
评论
9 楼 wuhongyu 2011-08-23  
yangfuchao418 写道
yangfuchao418 写道
楼主解释下for(int i=0;i<this.poolSize;i++){ 
                    os.writeInt(i+1); 
                    os.writeLong(i*threadLength); 
                    if(i==this.poolSize-1){//最后一个线程的结束位置应为文件末端 
                        os.writeLong(contentLength); 
                    }else{ 
                        os.writeLong((i+1)*threadLength); 
                    } 
                    os.writeLong(i*threadLength); 
                    pool.submit(new DownLoadTask(file, tempFile, url, i+1,latch,reconnectTime,bufferSize)); 
                } 
                os.close(); 
            } 
那个i+1呢?

还有你那个超时连接貌似没啥作用


那个i+1是线程编号,上面第二点已经说了,超时重连在while循环里,如果出现异常会在catch中跑个continue,重新执行while中的内容,达到超时重连的效果
8 楼 yangfuchao418 2011-08-18  
yangfuchao418 写道
楼主解释下for(int i=0;i<this.poolSize;i++){ 
                    os.writeInt(i+1); 
                    os.writeLong(i*threadLength); 
                    if(i==this.poolSize-1){//最后一个线程的结束位置应为文件末端 
                        os.writeLong(contentLength); 
                    }else{ 
                        os.writeLong((i+1)*threadLength); 
                    } 
                    os.writeLong(i*threadLength); 
                    pool.submit(new DownLoadTask(file, tempFile, url, i+1,latch,reconnectTime,bufferSize)); 
                } 
                os.close(); 
            } 
那个i+1呢?

还有你那个超时连接貌似没啥作用
7 楼 yangfuchao418 2011-08-18  
楼主解释下for(int i=0;i<this.poolSize;i++){ 
                    os.writeInt(i+1); 
                    os.writeLong(i*threadLength); 
                    if(i==this.poolSize-1){//最后一个线程的结束位置应为文件末端 
                        os.writeLong(contentLength); 
                    }else{ 
                        os.writeLong((i+1)*threadLength); 
                    } 
                    os.writeLong(i*threadLength); 
                    pool.submit(new DownLoadTask(file, tempFile, url, i+1,latch,reconnectTime,bufferSize)); 
                } 
                os.close(); 
            } 
那个i+1呢?
6 楼 wuhongyu 2011-05-09  
ltx 写道
有些地方不太明白,请教:
1:tempFile.seek(id*28 -;  
2://如果文件已存在,根据临时文件中记载的线程数量,继续上次的任务  
   latch = new CountDownLatch((int)tempFile.length()/28);  

这里的28是什么?是怎么计算的?


最近一直没上javaeye,今天才看到,不好意思,呵呵,道歉先!
我在临时文件中记录了每个线程的编号、起始位置、终止位置和当前位置,分别为一个int型(4个字节)和3个long型(8个字节),加起来就是28了。
5 楼 zhang964761032 2011-05-09  
找了好几例子都不能用,试试这个。。。。
4 楼 ltx 2011-04-27  
有些地方不太明白,请教:
1:tempFile.seek(id*28 -;  
2://如果文件已存在,根据临时文件中记载的线程数量,继续上次的任务  
   latch = new CountDownLatch((int)tempFile.length()/28);  

这里的28是什么?是怎么计算的?
3 楼 doltter 2011-01-14  
好东西,我找了很久了,能用的没几个,这个很好
2 楼 qq123zhz 2011-01-14  
以前用java写过,曾经想用as也写一个,发现as实现比较难。。。
1 楼 felsenlee 2011-01-14  
没做过类似的功能,学习下

相关推荐

    android多线程断点续传

    在Android开发中,多线程断点续传是一项重要的技术,尤其在处理大文件下载时。这个技术的主要目的是提高下载效率并确保用户可以中断或恢复下载过程。以下将详细讲解多线程断点续传的概念、实现原理以及在Android中的...

    Android多线程断点续传下载

    在Android应用开发中,实现多线程断点续传下载是一项重要的技术,它涉及到网络编程、文件操作以及并发处理等多个方面。以下将详细介绍这个主题的相关知识点。 首先,我们需要理解“多线程”。在Android系统中,为了...

    FTP、HTTP 多线程断点续传下载文件.rar

    标题中的“FTP、HTTP 多线程断点续传下载文件.rar”暗示了这是一个关于网络协议(FTP和HTTP)在实现多线程下载时如何支持断点续传功能的资源包。这个压缩文件可能包含了一个或者多个示例程序、文档或教程,用于解释...

    c#多线程断点续传

    在提供的压缩包文件中,可能包含了一个名为“download”的示例项目,这可能是用于演示上述多线程断点续传功能的源代码。通过分析和运行这个项目,你可以更直观地理解这两种技术的结合应用。 总之,多线程和断点续传...

    java多线程断点续传下载

    Java多线程断点续传下载是一个复杂但实用的技术,尤其在处理大文件或网络不稳定时,能够提高用户体验并优化资源利用。以下是对这个主题的详细解析: **1. Java多线程** Java多线程是指在一个Java应用程序中同时执行...

    java ftp 多线程 断点续传等知识

    而"多线程"和"断点"这两个文件名可能是指相关示例代码或文档,可以进一步帮助你理解和实践Java FTP的多线程下载和断点续传。 在实际应用中,还需要考虑其他因素,如错误处理、网络状况的监控、文件完整性检查等。...

    android多线程断点续传下载

    在Android开发中,多线程断点续传下载是一项重要的技术,它允许用户在暂停、关闭应用或设备重启后从上次中断的地方继续下载大文件,提高用户体验并节省网络资源。以下将详细介绍这一技术的关键知识点: 1. **多线程...

    Xutils框架进行多线程断点续传(详细注解)

    在这个主题中,我们将专注于使用Xutils实现多线程断点续传的功能,这对于大文件下载尤其重要。 首先,断点续传是一种在网络不稳定或者设备意外断电的情况下,能够从上次中断的位置继续下载的技术。它通过保存已下载...

    多线程断点续传(基于HTTP协议).zip_http 断点上传_http 断点续传_多线程断点续传_断点上传_断点续传

    同时,"【成品】多线程断点续传工具.jar"可能是一个可执行的Java应用程序,它提供了用户界面或者命令行工具,让用户可以方便地进行断点续传的文件上传。 多线程断点续传进一步提高了文件传输的效率。通过将文件分割...

    java多线程断点续传[借鉴].pdf

    总结来说,Java实现的多线程断点续传涉及的技术点包括: 1. 并发编程:使用`ExecutorService`、`CountDownLatch`进行线程管理和同步。 2. 文件操作:分析和合并临时文件,实现断点续传。 3. 网络I/O:通过`...

    Android多线程断点续传下载+在线播放音乐

    在Android应用开发中,实现“多线程断点续传下载+在线播放音乐”涉及到多个关键技术,主要包括网络编程、文件操作、多线程处理、内存管理以及多媒体播放等。以下是对这些知识点的详细阐述: 1. **多线程下载**: ...

    多线程断点续传下载实现

    在Android开发中,多线程断点续传技术是一种提高下载效率并优化用户体验的重要方法。这一技术主要应用于大型文件的下载场景,如游戏、应用程序或高清视频等,它允许用户在下载过程中随时暂停,之后能从中断的地方...

    点对点多线程断点续传的实现

    在点对点网络中实现多线程断点续传,主要涉及以下几个关键技术点: 1. **网络连接与通信协议**:P2P网络通常使用TCP或UDP协议进行通信。TCP保证了数据的可靠传输,适合断点续传,而UDP则提供了更高的传输速度,但...

    java实现FTP多线程断点续传

    ### Java实现FTP多线程断点续传:深入解析与技术要点 在现代软件开发中,数据传输是一项基本且关键的任务,特别是在处理大文件时,断点续传功能显得尤为重要。断点续传允许在网络连接中断后恢复传输,避免了重新...

    Android多线程断点续传下载网络上的音/视频等各种文件

    在Android开发中,实现多线程断点续传下载网络上的音视频文件是一项重要的技能,尤其对于提升用户体验和节省用户流量至关重要。断点续传允许用户在暂停或因网络问题中断下载后,从上次停止的位置继续,而多线程则能...

    一个支持多线程断点续传功能的Android下载工具.zip

    在Android平台上,开发一款支持多线程断点续传功能的下载工具是一项技术挑战,它涉及到网络编程、文件处理以及线程管理等多个方面。这款名为"DownloadHelper"的项目,显然是一个致力于解决这些问题的开源解决方案。 ...

    安卓多线程断点续传

    在安卓开发中,多线程断点续传是一项重要的技术,尤其在处理大文件下载时,它可以提高下载效率,同时确保在下载过程中如果中断,可以从上次停止的地方继续,避免了数据的重复下载。本文将详细讲解安卓多线程断点续传...

Global site tag (gtag.js) - Google Analytics