Java 文件多线程下载

         最近在做文件下载这块的东西,研究了一下多线程文件下载这块的知识。这里只说一下原理,具体实现请看代码,已经写了注释了。

主要原理

         为了加快下载速度,每个文件固定N个线程来下载,然后每个线程负责下载该文件的某一部分,比如文件大小90M,用3个线程来下载,那么第一个线程负责下载文件的长度范围:0-30*1024*1024-1,第二个线程负责下载文件的长度范围:30*1024*1024-60*1024*1024-1,第三个线程负责下载文件的长度范围:60*1024*1024-90*1024*1024-1,3个线程下载完后就合成了整个文件。这里需要用到Http中的ContentLength和Range请求头,ContentLength对应文件的总长度,Range头用来请求文件某一子块的内容,例如:Range 0-10000,表示请求该文件0-10000字节的内容。

具体代码如下:

package com.ricky.java.common.download.test;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.zip.GZIPInputStream;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.params.CoreConnectionPNames;

import com.ricky.java.common.download.file.http.HttpClientManager;
import com.ricky.java.common.download.file.util.Constants;

public class Downloader {
	
	private String url;	// 目标地址
	private File file;	// 本地文件
	private static final int THREAD_AMOUNT = 3;					// 线程数
	private static final String DIR_PATH = "D:/download/file";		// 下载目录
	private long threadLen;										// 每个线程下载多少

	private HttpClient mHttpClient = HttpClientManager.getHttpClient();
	
	public Downloader(String address) throws IOException {		// 通过构造函数传入下载地址
		url = address;
		file = new File(DIR_PATH, address.substring(address.lastIndexOf("?") + 1)+".xml");
	}

	public void download() throws IOException {
		
		long totalLen = getContentLength(url); 							// 获取文件长度
		threadLen = (totalLen + THREAD_AMOUNT - 1) / THREAD_AMOUNT;			// 计算每个线程要下载的长度
		
		System.out.println("totalLen="+totalLen+"***threadLen="+threadLen);
		
		RandomAccessFile raf = new RandomAccessFile(file, "rws");			// 在本地创建一个和服务端大小相同的文件
		raf.setLength(totalLen);											// 设置文件的大小
		raf.close();
		
		for (int i = 0; i < THREAD_AMOUNT; i++)								// 开启3条线程, 每个线程下载一部分数据到本地文件中
			new DownloadThread(i).start();
	}
	
	public long getContentLength(String address) {
		HttpGet httpget = null;
		try {
			httpget = new HttpGet(address);
			httpget.setHeader("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.79 Safari/537.1");
			httpget.setHeader("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
			
			httpget.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT,Constants.SO_TIMEOUT);
			httpget.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,Constants.CONNECTION_TIMEOUT);
			httpget.getParams().setParameter("http.protocol.cookie-policy",CookiePolicy.BROWSER_COMPATIBILITY);
			
			HttpResponse response = mHttpClient.execute(httpget);
			
			int status = response.getStatusLine().getStatusCode();
			
			if (status == HttpStatus.SC_OK) {
				
				return response.getEntity().getContentLength();
			}
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(httpget!=null){
				httpget.abort();
			}
		}
		return 0;
	}

	private class DownloadThread extends Thread {
		private int id; 
		public DownloadThread(int id) {
			this.id = id;
		}
		public void run() {
			long start = id * threadLen;						// 起始位置
			long end = id * threadLen + threadLen - 1;		// 结束位置
			System.out.println("线程" + id + ": " + start + "-" + end);
			
			HttpGet httpget = null;
			try {
				httpget = new HttpGet(url);
				httpget.setHeader("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.79 Safari/537.1");
				httpget.setHeader("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
				
				httpget.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT,Constants.SO_TIMEOUT);
				httpget.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,Constants.CONNECTION_TIMEOUT);
				httpget.getParams().setParameter("http.protocol.cookie-policy",CookiePolicy.BROWSER_COMPATIBILITY);
				
				HttpResponse response = mHttpClient.execute(httpget);
				
				int status = response.getStatusLine().getStatusCode();
				
				if (status == HttpStatus.SC_OK) {
					InputStream in = response.getEntity().getContent();
					Header contentEncoding = response.getFirstHeader("Content-Encoding");
					if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {
						System.out.println("gzip InputStream in post");
						in = new GZIPInputStream(in);
					}
					
					RandomAccessFile raf = new RandomAccessFile(file, "rws");
					raf.seek(start);
					
					byte[] buffer = new byte[1024];
					int len;
					while ((len = in.read(buffer)) != -1)
						raf.write(buffer, 0, len);
					raf.close();
					
					System.out.println("线程" + id + "下载完毕");
				}else{
					System.out.println("线程" + id + "请求失败,响应码="+status);
				}
				
			} catch (ClientProtocolException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}finally{
				if(httpget!=null){
					httpget.abort();
				}
			}
		}
	}
	
	public static void main(String[] args) throws IOException {
//		new Downloader("http://dldir1.qq.com/qqfile/qq/QQ6.2/12179/QQ6.2.exe").download();
		new Downloader("http://api.t.dianping.com/n/api.xml?cityId=1").download();
	}
}


HttpClientManager.java

package com.ricky.java.common.download.file.http;

import org.apache.http.client.HttpClient;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;

import com.ricky.java.common.download.file.util.Constants;

public class HttpClientManager {
	
	private static HttpParams httpParams;
	private static PoolingClientConnectionManager cm;

	/**
	 * 最大连接数
	 */
	public final static int MAX_TOTAL_CONNECTIONS = 200;
	/**
	 * 每个路由最大连接数
	 */
	public final static int MAX_ROUTE_CONNECTIONS = 300;
	

	static {
		SchemeRegistry schemeRegistry = new SchemeRegistry();
		schemeRegistry.register(
				new Scheme("http",80,PlainSocketFactory.getSocketFactory()));
		schemeRegistry.register(
				new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
		
		cm = new PoolingClientConnectionManager(schemeRegistry);
		cm.setMaxTotal(MAX_TOTAL_CONNECTIONS);
		cm.setDefaultMaxPerRoute(MAX_ROUTE_CONNECTIONS);
		
		HttpParams params = new BasicHttpParams();
		params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,Constants.CONNECTION_TIMEOUT);
		params.setParameter(CoreConnectionPNames.SO_TIMEOUT, Constants.SO_TIMEOUT);
	}

	public static HttpClient getHttpClient() {
		return new DefaultHttpClient(cm, httpParams);
	}

}



         如果想实现文件断点下载的话,只需要在记录每个线程当前下载了多少长度的内容即可,可以将其持久化到文件或数据库中保存起来,然后线程开始下载的时候都先读取一下它当前下载了多少就OK了。






1.得到服务器下载文件的大小,然后在本地设置一个临时文件和服务器端文件大小一致 a)获得访问网络地址 b)通过URL对象的openConnection()方法打开连接,返回一个连接对象 c)设置请求头 i.setRequestMethod ii.setConnectTimeout iii.setReadTimeout d)判断是否响应成功 e)获取文件长度(getContentLength()) f)随机访问文件的读取与写入RandomAccessFile(file, mode) g)设置临时文件与服务器文件大小一致(setLength()) h)关闭临时文件 2.计算出每个线程下载的大小(开始位置,结束位置) a)计算出每个线程下载的大小 b)for循环,计算出每个线程的开始、结束位置 c)最后一个线程处理 3.每创建好一次就要开启线程下载 a)构造方法 b)通过URL对象的openConnection()方法打开连接,返回一个连接对象 c)设置请求头 i.setRequestMethod ii.setConnectTimeout d)判断是否响应成功(206) e)获取每个线程返回的流对象 f)随机访问文件的读取与写入RandomAccessFile(file, mode) g)指定开始位置 h)循环读取 i.保存每个线程下载位置 ii.记录每次下载位置 iii.关闭临时记录位置文件 iv.随机本地文件写入 v.记录已下载大小 i)关闭临时文件 j)关闭输入流 4.为了杀死线程还能继续下载的情况下,从本地文件上读取已经下载文件的开始位置 a)创建保存记录结束位置的文件 b)读取文件 c)将流转换为字符 d)获取记录位置 e)把记录位置赋给开始位置 5.当你的n个线程都下载完毕的时候我进行删除记录下载位置的缓存文件 a)线程下载完就减去 b)当没有正在运行的线程时切文件存在时删除文件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值