Java实现的断点续传功能的示例代码

 更新时间:2017年02月21日 08:22:34   作者:路代为说项  
本篇文章主要介绍了Java实现的断点续传功能的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

代码中已经加入了注释,需要的朋友可以直接参考代码中的注释。下面直接上功能实现的主要代码:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
 * Encode:UTF-8
 * 
 * Author:zhiming.xu
 * 
 * 多线程的断点下载程序,根据输入的url和指定线程数,来完成断点续传功能。
 * 
 * 每个线程支负责某一小段的数据下载;再通过RandomAccessFile完成数据的整合。
 */
public class MultiTheradDownLoad {

  private String filepath = null;
  private String filename = null;
  private String tmpfilename = null;

  private int threadNum = 0;

  private CountDownLatch latch = null;//设置一个计数器,代码内主要用来完成对缓存文件的删除

  private long fileLength = 0l;
  private long threadLength = 0l;
  private long[] startPos;//保留每个线程下载数据的起始位置。
  private long[] endPos;//保留每个线程下载数据的截止位置。

  private boolean bool = false;

  private URL url = null;

  //有参构造函数,先构造需要的数据
  public MultiTheradDownLoad(String filepath, int threadNum) {
    this.filepath = filepath;
    this.threadNum = threadNum;
    startPos = new long[this.threadNum];
    endPos = new long[this.threadNum];
    latch = new CountDownLatch(this.threadNum);
  }

  /*
   * 组织断点续传功能的方法
   */
  public void downloadPart() {

    File file = null;
    File tmpfile = null;
    HttpURLConnection httpcon = null;

    //在请求url内获取文件资源的名称;此处没考虑文件名为空的情况,此种情况可能需使用UUID来生成一个唯一数来代表文件名。
    filename = filepath.substring(filepath.lastIndexOf('/') + 1, filepath
        .contains("?") ? filepath.lastIndexOf('?') : filepath.length());
    tmpfilename = filename + "_tmp";

    try {
      url = new URL(filepath);
      httpcon = (HttpURLConnection) url.openConnection();

      setHeader(httpcon);
      fileLength = httpcon.getContentLengthLong();//获取请求资源的总长度。

      file = new File(filename);
      tmpfile = new File(tmpfilename);

      threadLength = fileLength / threadNum;//每个线程需下载的资源大小。
      System.out.println("fileName: " + filename + " ," + "fileLength= "
          + fileLength + " the threadLength= " + threadLength);

      if (file.exists() && file.length() == fileLength) {
        System.out
            .println("the file you want to download has exited!!");
        return;
      } else {
        setBreakPoint(startPos, endPos, tmpfile);
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < threadNum; i++) {
          exec.execute(new DownLoadThread(startPos[i], endPos[i],
              this, i, tmpfile, latch));
        }
        latch.await();//当你的计数器减为0之前,会在此处一直阻塞。
        exec.shutdown();
      }
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    if (file.length() == fileLength) {
      if (tmpfile.exists()) {
        System.out.println("delect the temp file!!");
        tmpfile.delete();
      }
    }
  }

  /*
   * 断点设置方法,当有临时文件时,直接在临时文件中读取上次下载中断时的断点位置。没有临时文件,即第一次下载时,重新设置断点。
   * 
   * rantmpfile.seek()跳转到一个位置的目的是为了让各个断点存储的位置尽量分开。
   * 
   * 这是实现断点续传的重要基础。
   */
  private void setBreakPoint(long[] startPos, long[] endPos, File tmpfile) {
    RandomAccessFile rantmpfile = null;
    try {
      if (tmpfile.exists()) {
        System.out.println("the download has continued!!");
        rantmpfile = new RandomAccessFile(tmpfile, "rw");
        for (int i = 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("the Array content in the exit file: ");
          System.out.println("thre thread" + (i + 1) + " startPos:"
              + startPos[i] + ", endPos: " + endPos[i]);
        }
      } else {
        System.out.println("the tmpfile is not available!!");
        rantmpfile = new RandomAccessFile(tmpfile, "rw");
        
        //最后一个线程的截止位置大小为请求资源的大小
        for (int i = 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("the Array content: ");
          System.out.println("thre thread" + (i + 1) + " startPos:"
              + startPos[i] + ", endPos: " + endPos[i]);
        }
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (rantmpfile != null) {
          rantmpfile.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
  
  /*
   * 实现下载功能的内部类,通过读取断点来设置向服务器请求的数据区间。
   */
  class DownLoadThread implements Runnable {

    private long startPos;
    private long endPos;
    private MultiTheradDownLoad task = null;
    private RandomAccessFile downloadfile = null;
    private int id;
    private File tmpfile = null;
    private RandomAccessFile rantmpfile = null;
    private CountDownLatch latch = null;

    public DownLoadThread(long startPos, long endPos,
        MultiTheradDownLoad task, int id, File tmpfile,
        CountDownLatch latch) {
      this.startPos = startPos;
      this.endPos = endPos;
      this.task = task;
      this.tmpfile = tmpfile;
      try {
        this.downloadfile = new RandomAccessFile(this.task.filename,
            "rw");
        this.rantmpfile = new RandomAccessFile(this.tmpfile, "rw");
      } catch (FileNotFoundException e) {
        e.printStackTrace();
      }
      this.id = id;
      this.latch = latch;
    }

    @Override
    public void run() {

      HttpURLConnection httpcon = null;
      InputStream is = null;
      int length = 0;

      System.out.println("the thread " + id + " has started!!");

      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
                    + " the total size:---- "
                    + (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("the thread ---" + id
                  + " has done!!");
              latch.countDown();//计数器自减
              break;
            }

            is = httpcon.getInputStream();//获取服务器返回的资源流
            long count = 0l;
            byte[] buf = new byte[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("the thread " + id
                + " total load count: " + count);
            
            //关闭流
            is.close();
            httpcon.disconnect();
            downloadfile.close();
            rantmpfile.close();
          }
          latch.countDown();//计数器自减
          System.out.println("the thread " + id + " has done!!");
          break;
        } catch (IOException e) {
          e.printStackTrace();
        } finally {
          try {
            if (is != null) {
              is.close();
            }
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }

  /*
   * 为一个HttpURLConnection模拟请求头,伪装成一个浏览器发出的请求
   */
  private void setHeader(HttpURLConnection con) {
    con.setRequestProperty(
        "User-Agent",
        "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/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, 02 Jan 2009 17:00:05 GMT");
    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");
  }
}

下面是测试代码:

public class DownLoadTest {

  /**
   * @param args
   */
  public static void main(String[] args) {
    
    String filepath = "http://127.0.0.1:8080/file/loadfile.mkv";
    MultiTheradDownLoad load = new MultiTheradDownLoad(filepath ,4);  
    load.downloadPart();  
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • 浅谈MyBatis通用Mapper实现原理

    浅谈MyBatis通用Mapper实现原理

    这篇文章主要介绍了浅谈MyBatis通用Mapper实现原理,本文会先介绍通用 Mapper 的简单原理,然后使用最简单的代码来实现这个过程。感兴趣的小伙伴们可以参考一下
    2018-10-10
  • SpringBoot实现评论回复功能(数据库设计)

    SpringBoot实现评论回复功能(数据库设计)

    这篇文章主要介绍了SpringBoot实现评论回复功能(数据库设计),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • java中用数组实现环形队列的示例代码

    java中用数组实现环形队列的示例代码

    这篇文章主要介绍了java中用数组实现环形队列的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • springboot解决java.lang.ArrayStoreException异常

    springboot解决java.lang.ArrayStoreException异常

    这篇文章介绍了springboot解决java.lang.ArrayStoreException异常的方法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-12-12
  • java 四舍五入保留小数的实现方法

    java 四舍五入保留小数的实现方法

    下面小编就为大家带来一篇java 四舍五入保留小数的实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-09-09
  • 在SpringBoot下读取自定义properties配置文件的方法

    在SpringBoot下读取自定义properties配置文件的方法

    这篇文章主要介绍了在SpringBoot下读取自定义properties配置文件的方法,文中涉及到了Spring-boot中读取config配置文件的两种方式,需要的朋友可以参考下
    2017-12-12
  • java开发分布式服务框架Dubbo调用过程

    java开发分布式服务框架Dubbo调用过程

    这篇文章主要为大家介绍了java开发分布式服务框架Dubbo调用过程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2021-11-11
  • SpringBoot自动装配原理小结

    SpringBoot自动装配原理小结

    Spring Boot主要作用就是简化Spring应用的开发,开发者只需要通过少量代码就可以创建一个Spring应用,而达到这一目的最核心的思想就是约定优于配置。
    2021-05-05
  • MyBatis mapping类基本用法

    MyBatis mapping类基本用法

    这篇文章主要为大家介绍了MyBatis mapping类基本用法示例详解,
    有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • mybatis的大于小于号转义符号一览

    mybatis的大于小于号转义符号一览

    这篇文章主要介绍了mybatis的大于小于号转义符号一览,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08

最新评论