Java实现的断点续传功能的示例代码
代码中已经加入了注释,需要的朋友可以直接参考代码中的注释。下面直接上功能实现的主要代码:
importjava.io.File; importjava.io.FileNotFoundException; importjava.io.IOException; importjava.io.InputStream; importjava.io.RandomAccessFile; importjava.net.HttpURLConnection; importjava.net.MalformedURLException; importjava.net.URL; importjava.util.concurrent.CountDownLatch; importjava.util.concurrent.ExecutorService; importjava.util.concurrent.Executors; /* *Encode:UTF-8 * *Author:zhiming.xu * *多线程的断点下载程序,根据输入的url和指定线程数,来完成断点续传功能。 * *每个线程支负责某一小段的数据下载;再通过RandomAccessFile完成数据的整合。 */ publicclassMultiTheradDownLoad{ privateStringfilepath=null; privateStringfilename=null; privateStringtmpfilename=null; privateintthreadNum=0; privateCountDownLatchlatch=null;//设置一个计数器,代码内主要用来完成对缓存文件的删除 privatelongfileLength=0l; privatelongthreadLength=0l; privatelong[]startPos;//保留每个线程下载数据的起始位置。 privatelong[]endPos;//保留每个线程下载数据的截止位置。 privatebooleanbool=false; privateURLurl=null; //有参构造函数,先构造需要的数据 publicMultiTheradDownLoad(Stringfilepath,intthreadNum){ this.filepath=filepath; this.threadNum=threadNum; startPos=newlong[this.threadNum]; endPos=newlong[this.threadNum]; latch=newCountDownLatch(this.threadNum); } /* *组织断点续传功能的方法 */ publicvoiddownloadPart(){ Filefile=null; Filetmpfile=null; HttpURLConnectionhttpcon=null; //在请求url内获取文件资源的名称;此处没考虑文件名为空的情况,此种情况可能需使用UUID来生成一个唯一数来代表文件名。 filename=filepath.substring(filepath.lastIndexOf('/')+1,filepath .contains("?")?filepath.lastIndexOf('?'):filepath.length()); tmpfilename=filename+"_tmp"; try{ url=newURL(filepath); httpcon=(HttpURLConnection)url.openConnection(); setHeader(httpcon); fileLength=httpcon.getContentLengthLong();//获取请求资源的总长度。 file=newFile(filename); tmpfile=newFile(tmpfilename); threadLength=fileLength/threadNum;//每个线程需下载的资源大小。 System.out.println("fileName:"+filename+","+"fileLength=" +fileLength+"thethreadLength="+threadLength); if(file.exists()&&file.length()==fileLength){ System.out .println("thefileyouwanttodownloadhasexited!!"); return; }else{ setBreakPoint(startPos,endPos,tmpfile); ExecutorServiceexec=Executors.newCachedThreadPool(); for(inti=0;i<threadNum;i++){ exec.execute(newDownLoadThread(startPos[i],endPos[i], this,i,tmpfile,latch)); } latch.await();//当你的计数器减为0之前,会在此处一直阻塞。 exec.shutdown(); } }catch(MalformedURLExceptione){ e.printStackTrace(); }catch(IOExceptione){ e.printStackTrace(); }catch(InterruptedExceptione){ e.printStackTrace(); } if(file.length()==fileLength){ if(tmpfile.exists()){ System.out.println("delectthetempfile!!"); tmpfile.delete(); } } } /* *断点设置方法,当有临时文件时,直接在临时文件中读取上次下载中断时的断点位置。没有临时文件,即第一次下载时,重新设置断点。 * *rantmpfile.seek()跳转到一个位置的目的是为了让各个断点存储的位置尽量分开。 * *这是实现断点续传的重要基础。 */ privatevoidsetBreakPoint(long[]startPos,long[]endPos,Filetmpfile){ RandomAccessFilerantmpfile=null; try{ if(tmpfile.exists()){ System.out.println("thedownloadhascontinued!!"); rantmpfile=newRandomAccessFile(tmpfile,"rw"); for(inti=0;i<threadNum;i++){ rantmpfile.seek(8*i+8); startPos[i]=rantmpfile.readLong(); rantmpfile.seek(8*(i+1000)+16); endPos[i]=rantmpfile.readLong(); System.out.println("theArraycontentintheexitfile:"); System.out.println("threthread"+(i+1)+"startPos:" +startPos[i]+",endPos:"+endPos[i]); } }else{ System.out.println("thetmpfileisnotavailable!!"); rantmpfile=newRandomAccessFile(tmpfile,"rw"); //最后一个线程的截止位置大小为请求资源的大小 for(inti=0;i<threadNum;i++){ startPos[i]=threadLength*i; if(i==threadNum-1){ endPos[i]=fileLength; }else{ endPos[i]=threadLength*(i+1)-1; } rantmpfile.seek(8*i+8); rantmpfile.writeLong(startPos[i]); rantmpfile.seek(8*(i+1000)+16); rantmpfile.writeLong(endPos[i]); System.out.println("theArraycontent:"); System.out.println("threthread"+(i+1)+"startPos:" +startPos[i]+",endPos:"+endPos[i]); } } }catch(FileNotFoundExceptione){ e.printStackTrace(); }catch(IOExceptione){ e.printStackTrace(); }finally{ try{ if(rantmpfile!=null){ rantmpfile.close(); } }catch(IOExceptione){ e.printStackTrace(); } } } /* *实现下载功能的内部类,通过读取断点来设置向服务器请求的数据区间。 */ classDownLoadThreadimplementsRunnable{ privatelongstartPos; privatelongendPos; privateMultiTheradDownLoadtask=null; privateRandomAccessFiledownloadfile=null; privateintid; privateFiletmpfile=null; privateRandomAccessFilerantmpfile=null; privateCountDownLatchlatch=null; publicDownLoadThread(longstartPos,longendPos, MultiTheradDownLoadtask,intid,Filetmpfile, CountDownLatchlatch){ this.startPos=startPos; this.endPos=endPos; this.task=task; this.tmpfile=tmpfile; try{ this.downloadfile=newRandomAccessFile(this.task.filename, "rw"); this.rantmpfile=newRandomAccessFile(this.tmpfile,"rw"); }catch(FileNotFoundExceptione){ e.printStackTrace(); } this.id=id; this.latch=latch; } @Override publicvoidrun(){ HttpURLConnectionhttpcon=null; InputStreamis=null; intlength=0; System.out.println("thethread"+id+"hasstarted!!"); while(true){ try{ httpcon=(HttpURLConnection)task.url.openConnection(); setHeader(httpcon); //防止网络阻塞,设置指定的超时时间;单位都是ms。超过指定时间,就会抛出异常 httpcon.setReadTimeout(20000);//读取数据的超时设置 httpcon.setConnectTimeout(20000);//连接的超时设置 if(startPos<endPos){ //向服务器请求指定区间段的数据,这是实现断点续传的根本。 httpcon.setRequestProperty("Range","bytes="+startPos +"-"+endPos); System.out .println("Thread"+id +"thetotalsize:----" +(endPos-startPos)); downloadfile.seek(startPos); if(httpcon.getResponseCode()!=HttpURLConnection.HTTP_OK &&httpcon.getResponseCode()!=HttpURLConnection.HTTP_PARTIAL){ this.task.bool=true; httpcon.disconnect(); downloadfile.close(); System.out.println("thethread---"+id +"hasdone!!"); latch.countDown();//计数器自减 break; } is=httpcon.getInputStream();//获取服务器返回的资源流 longcount=0l; byte[]buf=newbyte[1024]; while(!this.task.bool&&(length=is.read(buf))!=-1){ count+=length; downloadfile.write(buf,0,length); //不断更新每个线程下载资源的起始位置,并写入临时文件;为断点续传做准备 startPos+=length; rantmpfile.seek(8*id+8); rantmpfile.writeLong(startPos); } System.out.println("thethread"+id +"totalloadcount:"+count); //关闭流 is.close(); httpcon.disconnect(); downloadfile.close(); rantmpfile.close(); } latch.countDown();//计数器自减 System.out.println("thethread"+id+"hasdone!!"); break; }catch(IOExceptione){ e.printStackTrace(); }finally{ try{ if(is!=null){ is.close(); } }catch(IOExceptione){ e.printStackTrace(); } } } } } /* *为一个HttpURLConnection模拟请求头,伪装成一个浏览器发出的请求 */ privatevoidsetHeader(HttpURLConnectioncon){ con.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"); con.setRequestProperty("Accept-Language","en-us,en;q=0.7,zh-cn;q=0.3"); con.setRequestProperty("Accept-Encoding","aa"); con.setRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); con.setRequestProperty("Keep-Alive","300"); con.setRequestProperty("Connection","keep-alive"); con.setRequestProperty("If-Modified-Since", "Fri,02Jan200917:00:05GMT"); con.setRequestProperty("If-None-Match","\"1261d8-4290-df64d224\""); con.setRequestProperty("Cache-Control","max-age=0"); con.setRequestProperty("Referer", "http://www.skycn.com/soft/14857.html"); } }
下面是测试代码:
publicclassDownLoadTest{ /** *@paramargs */ publicstaticvoidmain(String[]args){ Stringfilepath="http://127.0.0.1:8080/file/loadfile.mkv"; MultiTheradDownLoadload=newMultiTheradDownLoad(filepath,4); load.downloadPart(); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。