断点续传的关键
这里只针对http协议。
1.http请求中可以设置RANGE属性,用来设置返回数据的其实位置和结束位置
2.每个线程对同一个文件,不同的位置进行写入。可用RandomAccessFile的seek(long pos)方法,设置文件读写的起始位置。
3当暂停下载时,需要保存每个线程下载的进度。写进中间文件。
4.继续下载时,读取上次下载的进度,继续下载。
本文代码部分复制于下网站
https://www.ibm.com/developerworks/cn/java/joy-down/index.html
SiteFileFetch: 负责整个下载任务的控制。
FileSplitterFetch :为SiteFileFetch的内部类,继承自Thread类。为下载任务的子任务。
SiteInfoBean :保存的路径,文件名,和url等基本信息
FileAccessI :对流的操作类
OptionDownTask :读取上次任务或中断任务是,写入中间文件的接口。
OptionDownTaskImpl:实现了OptionDownTask的接口,该类读取和保存中文件信息是基于对象流ObjectInputStream和ObjectOutputStream.
TaskPositionBean:用于保存下载进度的可序列化的对象。
TaskProgressBean:OptionDownTask接口的方法的传入或返回的实体类。
SiteFileFetch.java
/*
/*
* SiteFileFetch.java
*/
package com.net.program.breakpoint;
import java.io.*;
import java.net.*;
import java.text.NumberFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class SiteFileFetch extends Thread {
SiteInfoBean siteInfoBean = null; // 文件信息 Bean
long[] nStartPos; // 开始位置
long[] nEndPos; // 结束位置
FileSplitterFetch[] fileSplitterFetch; // 子线程对象
long nFileLength; // 文件长度
boolean bFirst = true; // 是否第一次取文件
boolean bStop = false; // 停止标志
File tmpFile; // 文件下载的临时信息
DataOutputStream output; // 输出到文件的输出流
OptionDownTask odt;
static AtomicLong atomicLong;
TaskProgressBean taskProgressBean;
CountDownLatch countDownLatch;// 等所有线程执行完后,判断是否需要写入中间文件。
public SiteFileFetch(SiteInfoBean bean, OptionDownTask odt) throws IOException {
siteInfoBean = bean;
// tmpFile = File.createTempFile ("zhong","1111",new
// File(bean.getSFilePath()));
tmpFile = new File(bean.getSFilePath() + File.separator + bean.getSFileName() + ".info");
atomicLong = new AtomicLong();
this.odt = odt;
if (tmpFile.exists()) {
bFirst = false;
taskProgressBean = odt.readTask();// 读取上次下载进度
nStartPos = taskProgressBean.getStarts();
nEndPos = taskProgressBean.getEnds();
// read_nPos();
} else {
nStartPos = new long[bean.getNSplitter()];
nEndPos = new long[bean.getNSplitter()];
}
countDownLatch = new CountDownLatch(nStartPos.length);// 初始化阀门,值为,线程个数
}
public void run() {
// 获得文件长度
// 分割文件
// 实例 FileSplitterFetch
// 启动 FileSplitterFetch 线程
// 等待子线程返回
nFileLength = getFileSize();
try {
if (bFirst) {
// 是否是第一次下载该文件,如果是分割任务
// nFileLength = getFileSize();
if (nFileLength == -1) {
System.err.println("File Length is not known!");
} else if (nFileLength == -2) {
System.err.println("File is not access!");
} else {
long threadDownSize = nFileLength / nStartPos.length;
for (int i = 0; i < nStartPos.length; i++) {
nStartPos[i] = (i == 0 ? 0 : (long) (i * threadDownSize) + 1);
nEndPos[i] = (i + 1) * threadDownSize;
}
/*
* for (int i = 0; i < nEndPos.length - 1; i++) { nEndPos[i]
* = nStartPos[i + 1]; }
*/
nEndPos[nEndPos.length - 1] = nFileLength;
}
}
// 启动子线程
fileSplitterFetch = new FileSplitterFetch[nStartPos.length];
long remain = 0;
for (int i = 0; i < nStartPos.length; i++) {
remain += nEndPos[i] - nStartPos[i];
fileSplitterFetch[i] = new FileSplitterFetch(siteInfoBean.getSSiteURL(),
siteInfoBean.getSFilePath() + File.separator + siteInfoBean.getSFileName(), nStartPos[i],
nEndPos[i], i);
Utility.log("Thread " + i + " , nStartPos = " + nStartPos[i] + ", nEndPos = " + nEndPos[i]);
fileSplitterFetch[i].start();
}
// 初始化文件下载进度
atomicLong.set(nEndPos[nEndPos.length - 1] - remain);
// 用来读取控制台数据,如果输入stop,则停止当前任务,将停止表示设为true
new Thread() {
@Override
public void run() {