您的位置:首页 > 编程语言 > Java开发

Java 多线程断点下载文件

2011-09-30 18:25 435 查看
基本原理:利用URLConnection获取要下载文件的长度、头部等相关信息,并设置响应的头部信息。并且通过URLConnection获取输入流,将文件分成指定的块,每一块单独开辟一个线程完成数据的读取、写入。通过输入流读取下载文件的信息,然后将读取的信息用RandomAccessFile随机写入到本地文件中。同时,每个线程写入的数据都文件指针也就是写入数据的长度,需要保存在一个临时文件中。这样当本次下载没有完成的时候,下次下载的时候就从这个文件中读取上一次下载的文件长度,然后继续接着上一次的位置开始下载。并且将本次下载的长度写入到这个文件中。个人博客:http://hoojo.cnblogs.comhttp://blog.csdn.net/IBM_hoojoemail:hoojo_@126.com

一、下载文件信息类、实体

封装即将下载资源的信息

[code]packagecom.hoo.entity;
/**
*<b>function:</b>下载文件信息类
*@authorhoojo
*@createDate2011-9-21下午05:14:58
*@fileDownloadInfo.java
*@packagecom.hoo.entity
*@projectMultiThreadDownLoad
*@blog
'target='_blank'>http://blog.csdn.net/IBM_hoojo
*@emailhoojo_@126.com
*@version1.0
*/
publicclassDownloadInfo{
//下载文件url
privateStringurl;
//下载文件名称
privateStringfileName;
//下载文件路径
privateStringfilePath;
//分成多少段下载,每一段用一个线程完成下载
privateintsplitter;
//下载文件默认保存路径
privatefinalstaticStringFILE_PATH="C:/temp";
//默认分块数、线程数
privatefinalstaticintSPLITTER_NUM=5;
publicDownloadInfo(){
super();
}
/**
*@paramurl下载地址
*/
publicDownloadInfo(Stringurl){
this(url,null,null,SPLITTER_NUM);
}
/**
*@paramurl下载地址url
*@paramsplitter分成多少段或是多少个线程下载
*/
publicDownloadInfo(Stringurl,intsplitter){
this(url,null,null,splitter);
}
/***
*@paramurl下载地址
*@paramfileName文件名称
*@paramfilePath文件保存路径
*@paramsplitter分成多少段或是多少个线程下载
*/
publicDownloadInfo(Stringurl,StringfileName,StringfilePath,intsplitter){
super();
if(url==null||"".equals(url)){
thrownewRuntimeException("urlisnotnull!");
}
this.url=url;
this.fileName=(fileName==null||"".equals(fileName))?getFileName(url):fileName;
this.filePath=(filePath==null||"".equals(filePath))?FILE_PATH:filePath;
this.splitter=(splitter<1)?SPLITTER_NUM:splitter;
}
/**
*<b>function:</b>通过url获得文件名称
*@authorhoojo
*@createDate2011-9-30下午05:00:00
*@paramurl
*@return
*/
privateStringgetFileName(Stringurl){
returnurl.substring(url.lastIndexOf("/")+1,url.length());
}
publicStringgetUrl(){
returnurl;
}
publicvoidsetUrl(Stringurl){
if(url==null||"".equals(url)){
thrownewRuntimeException("urlisnotnull!");
}
this.url=url;
}
publicStringgetFileName(){
returnfileName;
}
publicvoidsetFileName(StringfileName){
this.fileName=(fileName==null||"".equals(fileName))?getFileName(url):fileName;
}
publicStringgetFilePath(){
returnfilePath;
}
publicvoidsetFilePath(StringfilePath){
this.filePath=(filePath==null||"".equals(filePath))?FILE_PATH:filePath;
}
publicintgetSplitter(){
returnsplitter;
}
publicvoidsetSplitter(intsplitter){
this.splitter=(splitter<1)?SPLITTER_NUM:splitter;
}
@Override
publicStringtoString(){
returnthis.url+"#"+this.fileName+"#"+this.filePath+"#"+this.splitter;
}
}
[/code]

二、随机写入一段文件


[code]packagecom.hoo.download;
importjava.io.IOException;
importjava.io.RandomAccessFile;
/**
*<b>function:</b>写入文件、保存文件
*@authorhoojo
*@createDate2011-9-21下午05:44:02
*@fileSaveItemFile.java
*@packagecom.hoo.download
*@projectMultiThreadDownLoad
*@blog
'target='_blank'>http://blog.csdn.net/IBM_hoojo
*@emailhoojo_@126.com
*@version1.0
*/
publicclassSaveItemFile{
//存储文件
privateRandomAccessFileitemFile;
publicSaveItemFile()throwsIOException{
this("",0);
}
/**
*@paramname文件路径、名称
*@parampos写入点位置position
*@throwsIOException
*/
publicSaveItemFile(Stringname,longpos)throwsIOException{
itemFile=newRandomAccessFile(name,"rw");
//在指定的pos位置开始写入数据
itemFile.seek(pos);
}
/**
*<b>function:</b>同步方法写入文件
*@authorhoojo
*@createDate2011-9-26下午12:21:22
*@parambuff缓冲数组
*@paramstart起始位置
*@paramlength长度
*@return
*/
publicsynchronizedintwrite(byte[]buff,intstart,intlength){
inti=-1;
try{
itemFile.write(buff,start,length);
i=length;
}catch(IOExceptione){
e.printStackTrace();
}
returni;
}
publicvoidclose()throwsIOException{
if(itemFile!=null){
itemFile.close();
}
}
}
[/code]
这个类主要是完成向本地的指定文件指针出开始写入文件,并返回当前写入文件的长度(文件指针)。这个类将被线程调用,文件被分成对应的块后,将被线程调用。每个线程都将会调用这个类完成文件的随机写入。

三、单个线程下载文件


[code]packagecom.hoo.download;
importjava.io.IOException;
importjava.io.InputStream;
importjava.net.HttpURLConnection;
importjava.net.MalformedURLException;
importjava.net.URL;
importjava.net.URLConnection;
importcom.hoo.util.LogUtils;
/**
*<b>function:</b>单线程下载文件
*@authorhoojo
*@createDate2011-9-22下午02:55:10
*@fileDownloadFile.java
*@packagecom.hoo.download
*@projectMultiThreadDownLoad
*@blog
'target='_blank'>http://blog.csdn.net/IBM_hoojo
*@emailhoojo_@126.com
*@version1.0
*/
publicclassDownloadFileextendsThread{
//下载文件url
privateStringurl;
//下载文件起始位置
privatelongstartPos;
//下载文件结束位置
privatelongendPos;
//线程id
privateintthreadId;
//下载是否完成
privatebooleanisDownloadOver=false;
privateSaveItemFileitemFile;
privatestaticfinalintBUFF_LENGTH=1024*8;
/**
*@paramurl下载文件url
*@paramname文件名称
*@paramstartPos下载文件起点
*@paramendPos下载文件结束点
*@paramthreadId线程id
*@throwsIOException
*/
publicDownloadFile(Stringurl,Stringname,longstartPos,longendPos,intthreadId)throwsIOException{
super();
this.url=url;
this.startPos=startPos;
this.endPos=endPos;
this.threadId=threadId;
//分块下载写入文件内容
this.itemFile=newSaveItemFile(name,startPos);
}
@Override
publicvoidrun(){
while(endPos>startPos&&!isDownloadOver){
try{
URLurl=newURL(this.url);
HttpURLConnectionconn=(HttpURLConnection)url.openConnection();
//设置连接超时时间为10000ms
conn.setConnectTimeout(10000);
//设置读取数据超时时间为10000ms
conn.setReadTimeout(10000);
setHeader(conn);
Stringproperty="bytes="+startPos+"-";
conn.setRequestProperty("RANGE",property);
//输出log信息
LogUtils.log("开始"+threadId+":"+property+endPos);
//printHeader(conn);
//获取文件输入流,读取文件内容
InputStreamis=conn.getInputStream();
byte[]buff=newbyte[BUFF_LENGTH];
intlength=-1;
LogUtils.log("#start#Thread:"+threadId+",startPos:"+startPos+",endPos:"+endPos);
while((length=is.read(buff))>0&&startPos<endPos&&!isDownloadOver){
//写入文件内容,返回最后写入的长度
startPos+=itemFile.write(buff,0,length);
}
LogUtils.log("#over#Thread:"+threadId+",startPos:"+startPos+",endPos:"+endPos);
LogUtils.log("Thread"+threadId+"isexecuteover!");
this.isDownloadOver=true;
}catch(MalformedURLExceptione){
e.printStackTrace();
}catch(IOExceptione){
e.printStackTrace();
}finally{
try{
if(itemFile!=null){
itemFile.close();
}
}catch(IOExceptione){
e.printStackTrace();
}
}
}
if(endPos<startPos&&!isDownloadOver){
LogUtils.log("Thread"+threadId+"startPos>endPos,notneeddownloadfile!");
this.isDownloadOver=true;
}
if(endPos==startPos&&!isDownloadOver){
LogUtils.log("Thread"+threadId+"startPos=endPos,notneeddownloadfile!");
this.isDownloadOver=true;
}
}
/**
*<b>function:</b>打印下载文件头部信息
*@authorhoojo
*@createDate2011-9-22下午05:44:35
*@paramconnHttpURLConnection
*/
publicstaticvoidprintHeader(URLConnectionconn){
inti=1;
while(true){
Stringheader=conn.getHeaderFieldKey(i);
i++;
if(header!=null){
LogUtils.info(header+":"+conn.getHeaderField(i));
}else{
break;
}
}
}
/**
*<b>function:</b>设置URLConnection的头部信息,伪装请求信息
*@authorhoojo
*@createDate2011-9-28下午05:29:43
*@paramcon
*/
publicstaticvoidsetHeader(URLConnectionconn){
conn.setRequestProperty("User-Agent","Mozilla/5.0(X11;U;Linuxi686;en-US;rv:1.9.0.3)Gecko/2008092510Ubuntu/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","utf-8");
conn.setRequestProperty("Accept-Charset","ISO-8859-1,utf-8;q=0.7,*;q=0.7");
conn.setRequestProperty("Keep-Alive","300");
conn.setRequestProperty("connnection","keep-alive");
conn.setRequestProperty("If-Modified-Since","Fri,02Jan200917:00:05GMT");
conn.setRequestProperty("If-None-Match","\"1261d8-4290-df64d224\"");
conn.setRequestProperty("Cache-conntrol","max-age=0");
conn.setRequestProperty("Referer","http://www.baidu.com");
}
publicbooleanisDownloadOver(){
returnisDownloadOver;
}
publiclonggetStartPos(){
returnstartPos;
}
publiclonggetEndPos(){
returnendPos;
}
}
[/code]
这个类主要是完成单个线程的文件下载,将通过URLConnection读取指定url的资源信息。然后用InputStream读取文件内容,然后调用调用SaveItemFile类,向本地写入当前要读取的块的内容。

四、分段多线程写入文件内容


[code]packagecom.hoo.download;
importjava.io.DataInputStream;
importjava.io.DataOutputStream;
importjava.io.File;
importjava.io.FileInputStream;
importjava.io.FileOutputStream;
importjava.io.IOException;
importjava.net.HttpURLConnection;
importjava.net.MalformedURLException;
importjava.net.URL;
importcom.hoo.entity.DownloadInfo;
importcom.hoo.util.LogUtils;
/**
*<b>function:</b>分批量下载文件
*@authorhoojo
*@createDate2011-9-22下午05:51:54
*@fileBatchDownloadFile.java
*@packagecom.hoo.download
*@projectMultiThreadDownLoad
*@blog
'target='_blank'>http://blog.csdn.net/IBM_hoojo
*@emailhoojo_@126.com
*@version1.0
*/
publicclassBatchDownloadFileimplementsRunnable{
//下载文件信息
privateDownloadInfodownloadInfo;
//一组开始下载位置
privatelong[]startPos;
//一组结束下载位置
privatelong[]endPos;
//休眠时间
privatestaticfinalintSLEEP_SECONDS=500;
//子线程下载
privateDownloadFile[]fileItem;
//文件长度
privateintlength;
//是否第一个文件
privatebooleanfirst=true;
//是否停止下载
privatebooleanstop=false;
//临时文件信息
privateFiletempFile;
publicBatchDownloadFile(DownloadInfodownloadInfo){
this.downloadInfo=downloadInfo;
StringtempPath=this.downloadInfo.getFilePath()+File.separator+downloadInfo.getFileName()+".position";
tempFile=newFile(tempPath);
//如果存在读入点位置的文件
if(tempFile.exists()){
first=false;
//就直接读取内容
try{
readPosInfo();
}catch(IOExceptione){
e.printStackTrace();
}
}else{
//数组的长度就要分成多少段的数量
startPos=newlong[downloadInfo.getSplitter()];
endPos=newlong[downloadInfo.getSplitter()];
}
}
@Override
publicvoidrun(){
//首次下载,获取下载文件长度
if(first){
length=this.getFileSize();//获取文件长度
if(length==-1){
LogUtils.log("filelengthisknow!");
stop=true;
}elseif(length==-2){
LogUtils.log("readfilelengthiserror!");
stop=true;
}elseif(length>0){
/**
*eg
*start:1,3,5,7,9
*end:3,5,7,9,length
*/
for(inti=0,len=startPos.length;i<len;i++){
intsize=i*(length/len);
startPos[i]=size;
//设置最后一个结束点的位置
if(i==len-1){
endPos[i]=length;
}else{
size=(i+1)*(length/len);
endPos[i]=size;
}
LogUtils.log("start-endPosition["+i+"]:"+startPos[i]+"-"+endPos[i]);
}
}else{
LogUtils.log("getfilelengthiserror,downloadisstop!");
stop=true;
}
}
//子线程开始下载
if(!stop){
//创建单线程下载对象数组
fileItem=newDownloadFile[startPos.length];//startPos.length=downloadInfo.getSplitter()
for(inti=0;i<startPos.length;i++){
try{
//创建指定个数单线程下载对象,每个线程独立完成指定块内容的下载
fileItem[i]=newDownloadFile(
downloadInfo.getUrl(),
this.downloadInfo.getFilePath()+File.separator+downloadInfo.getFileName(),
startPos[i],endPos[i],i
);
fileItem[i].start();//启动线程,开始下载
LogUtils.log("Thread:"+i+",startPos:"+startPos[i]+",endPos:"+endPos[i]);
}catch(IOExceptione){
e.printStackTrace();
}
}
//循环写入下载文件长度信息
while(!stop){
try{
writePosInfo();
LogUtils.log("downloading……");
Thread.sleep(SLEEP_SECONDS);
stop=true;
}catch(IOExceptione){
e.printStackTrace();
}catch(InterruptedExceptione){
e.printStackTrace();
}
for(inti=0;i<startPos.length;i++){
if(!fileItem[i].isDownloadOver()){
stop=false;
break;
}
}
}
LogUtils.info("Downloadtaskisfinished!");
}
}
/**
*将写入点数据保存在临时文件中
*@authorhoojo
*@createDate2011-9-23下午05:25:37
*@throwsIOException
*/
privatevoidwritePosInfo()throwsIOException{
DataOutputStreamdos=newDataOutputStream(newFileOutputStream(tempFile));
dos.writeInt(startPos.length);
for(inti=0;i<startPos.length;i++){
dos.writeLong(fileItem[i].getStartPos());
dos.writeLong(fileItem[i].getEndPos());
//LogUtils.info("["+fileItem[i].getStartPos()+"#"+fileItem[i].getEndPos()+"]");
}
dos.close();
}
/**
*<b>function:</b>读取写入点的位置信息
*@authorhoojo
*@createDate2011-9-23下午05:30:29
*@throwsIOException
*/
privatevoidreadPosInfo()throwsIOException{
DataInputStreamdis=newDataInputStream(newFileInputStream(tempFile));
intstartPosLength=dis.readInt();
startPos=newlong[startPosLength];
endPos=newlong[startPosLength];
for(inti=0;i<startPosLength;i++){
startPos[i]=dis.readLong();
endPos[i]=dis.readLong();
}
dis.close();
}
/**
*<b>function:</b>获取下载文件的长度
*@authorhoojo
*@createDate2011-9-26下午12:15:08
*@return
*/
privateintgetFileSize(){
intfileLength=-1;
try{
URLurl=newURL(this.downloadInfo.getUrl());
HttpURLConnectionconn=(HttpURLConnection)url.openConnection();
DownloadFile.setHeader(conn);
intstateCode=conn.getResponseCode();
//判断httpstatus是否为HTTP/1.1206PartialContent或者200OK
if(stateCode!=HttpURLConnection.HTTP_OK&&stateCode!=HttpURLConnection.HTTP_PARTIAL){
LogUtils.log("ErrorCode:"+stateCode);
return-2;
}elseif(stateCode>=400){
LogUtils.log("ErrorCode:"+stateCode);
return-2;
}else{
//获取长度
fileLength=conn.getContentLength();
LogUtils.log("FileLength:"+fileLength);
}
//读取文件长度
/*for(inti=1;;i++){
Stringheader=conn.getHeaderFieldKey(i);
if(header!=null){
if("Content-Length".equals(header)){
fileLength=Integer.parseInt(conn.getHeaderField(i));
break;
}
}else{
break;
}
}
*/
DownloadFile.printHeader(conn);
}catch(MalformedURLExceptione){
e.printStackTrace();
}catch(IOExceptione){
e.printStackTrace();
}
returnfileLength;
}
}
[/code]
这个类主要是完成读取指定url资源的内容,获取该资源的长度。然后将该资源分成指定的块数,将每块的起始下载位置、结束下载位置,分别保存在一个数组中。每块都单独开辟一个独立线程开始下载。在开始下载之前,需要创建一个临时文件,写入当前下载线程的开始下载指针位置和结束下载指针位置。

五、工具类、测试类

日志工具类


[code]packagecom.hoo.util;
/**
*<b>function:</b>日志工具类
*@authorhoojo
*@createDate2011-9-21下午05:21:27
*@fileLogUtils.java
*@packagecom.hoo.util
*@projectMultiThreadDownLoad
*@blog
'target='_blank'>http://blog.csdn.net/IBM_hoojo
*@emailhoojo_@126.com
*@version1.0
*/
publicabstractclassLogUtils{
publicstaticvoidlog(Objectmessage){
System.err.println(message);
}
publicstaticvoidlog(Stringmessage){
System.err.println(message);
}
publicstaticvoidlog(intmessage){
System.err.println(message);
}
publicstaticvoidinfo(Objectmessage){
System.out.println(message);
}
publicstaticvoidinfo(Stringmessage){
System.out.println(message);
}
publicstaticvoidinfo(intmessage){
System.out.println(message);
}
}
[/code]

下载工具类


[code]packagecom.hoo.util;
importcom.hoo.download.BatchDownloadFile;
importcom.hoo.entity.DownloadInfo;
/**
*<b>function:</b>分块多线程下载工具类
*@authorhoojo
*@createDate2011-9-28下午05:22:18
*@fileDownloadUtils.java
*@packagecom.hoo.util
*@projectMultiThreadDownLoad
*@blog
'target='_blank'>http://blog.csdn.net/IBM_hoojo
*@emailhoojo_@126.com
*@version1.0
*/
publicabstractclassDownloadUtils{
publicstaticvoiddownload(Stringurl){
DownloadInfobean=newDownloadInfo(url);
LogUtils.info(bean);
BatchDownloadFiledown=newBatchDownloadFile(bean);
newThread(down).start();
}
publicstaticvoiddownload(Stringurl,intthreadNum){
DownloadInfobean=newDownloadInfo(url,threadNum);
LogUtils.info(bean);
BatchDownloadFiledown=newBatchDownloadFile(bean);
newThread(down).start();
}
publicstaticvoiddownload(Stringurl,StringfileName,StringfilePath,intthreadNum){
DownloadInfobean=newDownloadInfo(url,fileName,filePath,threadNum);
LogUtils.info(bean);
BatchDownloadFiledown=newBatchDownloadFile(bean);
newThread(down).start();
}
}
[/code]

下载测试类


[code]packagecom.hoo.test;
importcom.hoo.util.DownloadUtils;
/**
*<b>function:</b>下载测试
*@authorhoojo
*@createDate2011-9-23下午05:49:46
*@fileTestDownloadMain.java
*@packagecom.hoo.download
*@projectMultiThreadDownLoad
*@blog
'target='_blank'>http://blog.csdn.net/IBM_hoojo
*@emailhoojo_@126.com
*@version1.0
*/
publicclassTestDownloadMain{
publicstaticvoidmain(String[]args){
/*DownloadInfobean=newDownloadInfo("http://i7.meishichina.com/Health/UploadFiles/201109/2011092116224363.jpg");
System.out.println(bean);
BatchDownloadFiledown=newBatchDownloadFile(bean);
newThread(down).start();*/
//DownloadUtils.download("http://i7.meishichina.com/Health/UploadFiles/201109/2011092116224363.jpg");
DownloadUtils.download("http://mp3.baidu.com/j?j=2&url=http%3A%2F%2Fzhangmenshiting2.baidu.com%2Fdata%2Fmusic%2F1669425%2F%25E9%2599%25B7%25E5%2585%25A5%25E7%2588%25B1%25E9%2587%258C%25E9%259D%25A2.mp3%3Fxcode%3D2ff36fb70737c816553396c56deab3f1","aa.mp3","c:/temp",5);
}
}
[/code]
多线程下载主要在第三部和第四部,其他的地方还是很好理解。源码中提供相应的注释了,便于理解。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: