本文共 11273 字,大约阅读时间需要 37 分钟。
前面写了单线程下载、断点续传、文件分隔与合并三个程序(具体可以参见我前面的程序),在这个程序的基础之上,我完成了多线程下载程序,并具备断点续传功能。
该程序具有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=""; 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(); } }转载地址:http://pflmi.baihongyu.com/