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{//下载文件urlprivateStringurl;//下载文件名称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;}@OverridepublicStringtoString(){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{//下载文件urlprivateStringurl;//下载文件起始位置privatelongstartPos;//下载文件结束位置privatelongendPos;//线程idprivateintthreadId;//下载是否完成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);}@Overridepublicvoidrun(){while(endPos>startPos&&!isDownloadOver){try{URLurl=newURL(this.url);HttpURLConnectionconn=(HttpURLConnection)url.openConnection();//设置连接超时时间为10000msconn.setConnectTimeout(10000);//设置读取数据超时时间为10000msconn.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()];}}@Overridepublicvoidrun(){//首次下载,获取下载文件长度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或者200OKif(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]
多线程下载主要在第三部和第四部,其他的地方还是很好理解。源码中提供相应的注释了,便于理解。
相关文章推荐
- Java 多线程断点下载文件
- Java 多线程断点下载文件
- 用java编写多线程ftp断点下载文件程序
- Java多线程断点下载多文件(窗口程序带进度条)
- Java 多线程断点下载文件
- 用java编写多线程ftp断点下载文件程序
- Java 多线程断点下载文件_详解
- Java 多线程断点下载文件
- Java 多线程断点下载文件
- Java多线程断点下载文件
- Java 多线程断点下载文件
- Java 多线程断点下载文件
- OSS实现多文件多线程的断点下载(java)
- 使用java实现http多线程断点下载文件(一)
- Java 多线程断点下载文件
- 使用java实现http多线程断点下载文件(二)
- 〖編程·Java〗Java 多线程断点下载文件
- Java 多线程断点下载文件_详解
- Java 多线程断点下载文件
- Java 多线程断点下载文件