欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-06 08:23:19
代码中已经加入了注释,需要的朋友可以直接参考代码中的注释。下面直接上功能实现的主要代码: import java.io.file; import 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();  
  }
}

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