您的位置:首页 > 编程语言 > Java开发

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

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

        该程序具有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();
  }
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息