博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JAVA写的多线程下载程序,并具有断点续传功能
阅读量:4219 次
发布时间:2019-05-26

本文共 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/

你可能感兴趣的文章
趣链 BitXHub跨链平台 (4)跨链网关“初介绍”
查看>>
C++ 字符串string操作
查看>>
MySQL必知必会 -- 了解SQL和MySQL
查看>>
MySQL必知必会 -- 排序检索数据 ORDER BY
查看>>
POJ 3087 解题报告
查看>>
POJ 2536 解题报告
查看>>
POJ 1154 解题报告
查看>>
POJ 1101 解题报告
查看>>
ACM POJ catalogues[转载]
查看>>
常见的排序算法
查看>>
hdu 3460 Ancient Printer(trie tree)
查看>>
KMP
查看>>
poj 3863Business Center
查看>>
Android编译系统简要介绍和学习计划
查看>>
Android编译系统环境初始化过程分析
查看>>
user2eng 笔记
查看>>
DRM in Android
查看>>
ARC MRC 变换
查看>>
Swift cell的自适应高度
查看>>
【linux】.fuse_hiddenXXXX 文件是如何生成的?
查看>>