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();
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。