`
JAVA天地
  • 浏览: 656230 次
  • 性别: Icon_minigender_1
  • 来自: 太原
文章分类
社区版块
存档分类
最新评论

JAVA写的多线程下载程序,并具有断点续传功能

阅读更多

前面写了单线程下载、断点续传、文件分隔与合并三个程序(具体可以参见我前面的程序),在这个程序的基础之上,我完成了多线程下载程序,并具备断点续传功能。

该程序具有5个文件:Main.java(主文件)、FileCombination.java(临时文件合并)、GetFileThread.java(网络文件获取)、MultiThreadGetFile.java(多线程下载调度程序)、PoliceThread.java(监视线程,确定所有的文件块都完成,并调用文件合并程序)。文件详细如下:

Main.java:

package MultiThread;
/**
* 程序主文件
*/
public class Main
{
String urlFile;//网络文件地址
int threadNum;//要启动下载的线程数
String localFileAddress;//要保存的本地地址,请保重该处没有名为"tmp"的文件夹
public Main()
{
/**
* 下面的由使用者自己设为定
*/
urlFile="http://www.netbox.cn/download/nbsetup.EXE";
threadNum=9;//要同时下载的线程数
localFileAddress="d:\\multiDownTest\\";
}
private void start()
{
Thread thread=new Thread(new MultiThreadGetFile(urlFile,threadNum,localFileAddress));
thread.start();
}
public static void main(String[] args)
{
Main main = new Main();
main.start();
}
}


FileCombination.java:

package MultiThread;
/**
* 合并文件:合并由拆分文件拆分的文件
* 要求将拆分文件放到一个文件夹中
* 主要利用随机文件读取和文件输入输出流
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;

import java.util.Arrays;
import java.util.StringTokenizer;

public class FileCombination extends Thread
{
String srcDirectory=null;//拆分文件存放的目录
String trueDirectory;//结果文件存放目录
String[] separatedFiles;//存放所有拆分文件名
String[][] separatedFilesAndSize;//存放所有拆分文件名及分件大小
int FileNum=0;//确定文件个数
String fileRealName="";//据拆分文件名确定现在原文件名
public FileCombination(String trueDirectory,String srcDirectory)
{
this.srcDirectory=srcDirectory;
this.trueDirectory=trueDirectory;
}
/**
*
* @param sFileName 任一一个拆分文件名
* @return 原文件名
*/
private String getRealName(String sFileName)
{
StringTokenizer st=new StringTokenizer(sFileName,".");
return st.nextToken()+"."+st.nextToken();
}
/**
* 取得指定拆分文件模块的文件大小
* @param FileName 拆分的文件名
* @return
*/
private long getFileSize(String FileName)
{
FileName=srcDirectory+"\\"+FileName;
return (new File(FileName).length());
}
/**
* 生成一些属性,做初使化
* @param drictory 拆分文件目录
*/
private void getFileAttribute(String drictory)
{
File file=new File(drictory);
separatedFiles=new String[file.list().length];//依文件数目动态生成一维数组,只有文件名
separatedFiles=file.list();
//依文件数目动态生成二维数组,包括文件名和文件大小
//第一维装文件名,第二维为该文件的字节大小
separatedFilesAndSize=new String[separatedFiles.length][2];
Arrays.sort(separatedFiles);//排序
FileNum=separatedFiles.length;//当前文件夹下面有多少个文件
for(int i=0;i<FileNum;i++)
{
separatedFilesAndSize[i][0]=separatedFiles[i];//文件名
separatedFilesAndSize[i][1]=String.valueOf(getFileSize(separatedFiles[i]));//文件大上
}
fileRealName=getRealName(separatedFiles[FileNum-1]);//取得文件分隔前的原文件名
}
/**
* 合并文件:利用随机文件读写
* @return true为成功合并文件
*/
private boolean CombFile()
{
RandomAccessFile raf=null;
long alreadyWrite=0;
FileInputStream fis=null;
int len=0;
byte[] bt=new byte[1024];
try
{
raf = new RandomAccessFile(trueDirectory+"\\"+fileRealName,"rw");
for(int i=0;i<FileNum;i++)
{
raf.seek(alreadyWrite);
//System.out.println("alreadyWrite:"+alreadyWrite);
fis=new FileInputStream(srcDirectory+"\\"+separatedFilesAndSize[i][0]);
while((len=fis.read(bt))>0)
{
raf.write(bt,0,len);
}
fis.close();
alreadyWrite=alreadyWrite+Long.parseLong(separatedFilesAndSize[i][1]);
}
raf.close();
}
catch (Exception e)
{
e.printStackTrace();
try
{
if(raf!=null)
raf.close();
if(fis!=null)
fis.close();
}
catch (IOException f)
{
f.printStackTrace();
}
return false;
}
return true;
}
public void deleteTmp()
{
for(int i=0;i<FileNum;i++)
{
File file=new File(srcDirectory+"\\"+separatedFilesAndSize[i][0]);
file.delete();
}
File file1=new File(srcDirectory);
file1.delete();
}
public void run()
{
getFileAttribute(srcDirectory);
CombFile();
deleteTmp();
}
}
GetFileThread.java:

package MultiThread;
/**
* 下载线程
* 原理:
* 根据传入的下载开始点以及文件下载结束点,利用HttpURLConnection的RANGE属性,
* 从网络文件开始下载,并结合判断是否下载的文件大小已经等于(文件下载结束点-下载开始点), *
* 这里结合断点续传原理,可以更快、更有效的下载文件
*/
import java.io.BufferedInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;

import java.io.IOException;

import java.io.RandomAccessFile;

import java.net.*;

public class GetFileThread extends Thread
{
long startPos,endPos;//传入的文件下载开始、结束点
String currentFileThreadName;//要带上完整的路径
String urlFile;//网络文件地址
int currentThread;//当前是那个线程,这主要是用于下载完成后将对应的检测标志设为true,表示下载完成
/**
*
* @param urlFile 网络文件地址
* @param startPos 网络开始下载点
* @param endPos 网络文件结点
* @param currentFileThreadName 当前线程的完程路径及名字
* @param currentThread 当前是第几个线程
*/
public GetFileThread(String urlFile,long startPos,long endPos,String currentFileThreadName,int currentThread)
{
this.startPos=startPos;
this.endPos=endPos;
this.currentFileThreadName=currentFileThreadName;
this.urlFile=urlFile;
this.currentThread=currentThread;
}
private boolean FileExist(String pathAndFile)
{
File file = new File(pathAndFile);
if (file.exists())
return true;
else
return false;
}

private long FileSize(String pathAndFile)
{
long fileSize=0;
File filet = new File(pathAndFile);
fileSize=filet.length();
return fileSize;
}

private void FileRename(String fName, String nName)
{
File file = new File(fName);
file.renameTo(new File(nName));
file.delete();
}

public void run()
{
URL url = null;
HttpURLConnection httpURLConnection = null;
DataOutputStream dos = null;
BufferedInputStream bis = null;
FileOutputStream fos = null;
String localFile = currentFileThreadName; //文件保存的地方及文件名,具体情况可以改
String localFile_tp = localFile + ".tp"; //未下载完文件加.tp扩展名,以便于区别
long fileSize = 0;//在断点续传中,用于取得当前文件已经下载的大小
int len = 0;
byte[] bt = new byte[1024];//缓冲区
//byte[] buffer=new byte[50*1024];
RandomAccessFile raFile = null;
long TotalSize = 0; //当前块要下载的文件总大小
try
{
url = new URL(urlFile);
httpURLConnection = (HttpURLConnection) url.openConnection();
//TotalSize = Long.parseLong(urlc.getHeaderField("Content-Length"));//取得网络文件大小
TotalSize=endPos-startPos;//取得要该块文件实际要写的大小
long downSize=0;//已经下载的大小
//确定临时文件是否存在
if (FileExist(localFile_tp)) //采用断点续传,这里的依据是看下载文件是否在本地有.tp有扩展名同名文件
{
System.out.println("文件续传中...");
fileSize=new File(localFile_tp).length();//取得已经下载的大小,以便确定随机写入的位置
downSize=fileSize;//下载大小
fileSize=fileSize+startPos;//取得文件开始写入点
//设置User-Agent
//urlc.setRequestProperty("User-Agent","NetFox");
/**
* httpURLConnection属性的设置一定要在得到输入流之前,否则会报已经连接的错误
*/
//设置断点续传的开始位置
//synchronized(new Object()){
httpURLConnection.setRequestProperty("RANGE", "bytes=" + fileSize + "-");
//urlc.setRequestProperty("RANGE", "bytes="+fileSize);//这样写不行,不能少了这个"-".
//设置接受信息
httpURLConnection.setRequestProperty("Accept",
"image/gif,image/x-xbitmap,application/msword,*/*");
//}
raFile = new RandomAccessFile(localFile_tp, "rw"); //随机方位读取
raFile.seek(downSize); //定位指针到fileSize位置
bis = new BufferedInputStream(httpURLConnection.getInputStream());
while ((len = bis.read(bt)) > 0)
{
if(downSize<(endPos-startPos))
{
downSize=downSize+len;
if(downSize>(endPos-startPos))
{
len=(int)((endPos-startPos)-(downSize-len));
}
raFile.write(bt, 0, len);
}
else
break;
}
//System.out.println("文件续传接收完毕!");
}
else if(!FileExist(localFile))//采用原始下载,但保证该文件没有下载
{
//设置断点续传的开始位置
httpURLConnection.setRequestProperty("RANGE", "bytes=" + startPos + "-");
bis = new BufferedInputStream(httpURLConnection.getInputStream());
fos = new FileOutputStream(localFile_tp); //没有下载完毕就将文件的扩展名命名.tp
dos = new DataOutputStream(fos);
//System.out.println("正在接收文件...");
while ((len = bis.read(bt)) > 0)
{
if(downSize<(endPos-startPos))//确定没有下载完毕
{
downSize=downSize+len;
if(downSize>(endPos-startPos))//如果当前下载的加上要下载的已经超过要求的下载范围
{
len=(int)((endPos-startPos)-(downSize-len));//就只取满足要求的下功部份
}
dos.write(bt, 0, len);//写文件
}
else
break;
}
}
if (bis != null)
bis.close();
if (dos != null)
dos.close();
if (fos != null)
fos.close();
if (raFile != null)
raFile.close();
//System.out.println("localFile_bak:" + FileSize(localFile_bak));
if (FileSize(localFile_tp) == TotalSize) //下载完毕后,将文件重命名
{
FileRename(localFile_tp, localFile);
}
MultiThreadGetFile.checkList[currentThread]=true;
}
catch (Exception e)
{
try
{
if (bis != null)
bis.close();
if (dos != null)
dos.close();
if (fos != null)
fos.close();
if (raFile != null)
raFile.close();
}
catch (IOException f)
{
f.printStackTrace();
}
e.printStackTrace();
}
}
}
MultiThreadGetFile.java:

package MultiThread;
/**
* 多线程下载调度程序
*/
import java.io.File;

import java.net.HttpURLConnection;
import java.net.URL;
import java.util.StringTokenizer;

public class MultiThreadGetFile extends Thread
{
long startPos=0,endPos=0;
String currentFileThreadName;//要带上完整的路径
String urlFile;//网络文件地址
String urlFileName;//网络文件名
String localFileAddress;//下载文件要存放的地址
int threadNum;//要同时下载的线程数
long[] eachThreadLength;//每个线程要下功的文件分块的大小
long urlFileLength;//网络文件的大小
URL url;
HttpURLConnection httpURLConnection;
public static boolean[] checkList;//检测线程
public MultiThreadGetFile(String urlFile,int threadNum,String localFileAddress)
{
this.urlFile=urlFile;
this.threadNum=threadNum;//要同时下载的线程数
this.localFileAddress=localFileAddress;

}
private void init_getEachThreadLength()//确定每个线程文件最终要写的文件在大小
{
long l;
l=urlFileLength/threadNum;
for(int i=0;i<threadNum;i++)
{
if(i==threadNum-1)//如果是分配最后一个线程了
{
eachThreadLength[i]=urlFileLength-i*l;
}
else
eachThreadLength[i]=l;
}
}
private String GetFileName(String file)
{
StringTokenizer st=new StringTokenizer(file,"/");
while(st.hasMoreTokens())
{
file=st.nextToken();
}
return file;
}
private void init()
{

if(!new File(localFileAddress+"tmp").mkdir())//创建一个临时文件夹
{
System.out.println("创建文件夹失败!");
}
eachThreadLength=new long[threadNum];
try
{
url=new URL(urlFile);
httpURLConnection=(HttpURLConnection)url.openConnection();
urlFileLength=Long.parseLong(httpURLConnection.getHeaderField("Content-Length"));
urlFileName=url.getFile();//取得在服务器上的路径及文件名
urlFileName=GetFileName(urlFileName);//只得文件名
init_getEachThreadLength();
httpURLConnection.disconnect();
checkList=new boolean[threadNum+1];
for(int i=1;i<=threadNum;i++)
{
if(i>1)
startPos=startPos+eachThreadLength[i-2];
endPos=startPos+eachThreadLength[i-1];
currentFileThreadName=localFileAddress+"tmp\\"+urlFileName+".part"+i;
//System.out.println("startPos:"+(startPos));
//System.out.println("endPos:"+(endPos));
//System.out.println("Size:"+(endPos-startPos));
Thread thread=new Thread(new GetFileThread(urlFile,startPos,endPos,currentFileThreadName,i));
thread.start();
checkList[i]=false;//表示该线程开始
}
Thread policeThread=new Thread(new PoliceThread(threadNum,localFileAddress,localFileAddress+"tmp"));
policeThread.start();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void run()
{
init();
}
}

PoliceThread.java:

package MultiThread;

/**
* 监视线程,检测其它的线程是否已经运行完毕
* 原理:
* 在MultiThreadGetFile里定义一个全局静态boolean数组,在启动每个GetFileThread
* 的时候,就将对应的数组的值设为false,当对应线程完成后就把对应的数组设为true,
* 在当前线程采用不停检测是否所有数组的值都为true,如是那就说明所有的线程已经运行完
* 毕,如果没有就继续检测。
* 等到所有的GetFileThread线程都完成后,那么就调用文件拼合线程,合并下载的文件块并删除
* 临时文件块。
*/
public class PoliceThread
extends Thread
{
int totalThread;
String localFileAddress;
String localFileAddress_tmp;

public PoliceThread(int totalThread, String localFileAddress,
String localFileAddress_tmp)
{
this.totalThread = totalThread;
this.localFileAddress = localFileAddress;
this.localFileAddress_tmp = localFileAddress_tmp;
}

public void run()
{
boolean isRun = true;
int allStop = 0;
while (isRun)
{
allStop=0;
for (int i = 1; i <= totalThread; i++)
{
if (MultiThreadGetFile.checkList[i] == true)
{
allStop++;
}
}
try
{
this.sleep(500);
}
catch (Exception e)
{
e.printStackTrace();
}
if (allStop == totalThread)
isRun = false;
}
Thread thread =
new Thread(new FileCombination(localFileAddress, localFileAddress_tmp));
thread.start();
}
}

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics