JAVA多线程断点续传下载

时间:2020-12-29 21:13:32

JAVA多线程断点续传下载    


      多个线程同时去下载一个资源,肯定比一个线程去下载一个资源速度快的多,所以现在我们要实现一下多个线程去下载一个资源,而断点下载的意思是,如果上一次因为某种原因,下载没有进行到底,再进行下一次下载的时候,可以在上一次下载的位置继续进行下载,而不需要又从文件的第一个字节开始下载。

下面简单的说一下多线程下载的步骤:

1、通过HttpURLConnection的getContentLength()获取要下载文件的总大小

2、根据所开启的线程的数量,将资源等分,比如这个文件总大小是6.1M,现在开启三个线程A、B、C去下载,所以A线程下载的范围是0~2M,B线程下载的范围是2~4M,C线程下载的范围是4~6.1M,我们可以让最后一个线程辛苦一点,多下0.1M。

3、在本地建立与线程数量对应的临时文件(positionFile)用于记录每个线程下载的位置,这样下次就可以从这个位置继续下载

4、如果每个线程全部下载完毕,则可以删除临时文件。


好了,下面是代码实现。

我把一个叫StringBuffer的视频放在tomcat服务器上,然后开启了4个线程去同时下载这个视频。

public class MutileThreaddownload {
// 线程的数量
private static int threadCount = 4;
// 每个下载区块的大小
private static long blockSize;
// 正在运行的线程的数量
private static int runningThreadCount;

public static void main(String[] args) {
try {
String downloadPath="http://localhost:8080/FormWorkTest/StringBuffer.avi";
URL downloadUrl=new URL(downloadPath);
HttpURLConnection coon=(HttpURLConnection) downloadUrl.openConnection();
coon.setRequestMethod("GET");
coon.setReadTimeout(5000);

int code = coon.getResponseCode();
if(code==200){
runningThreadCount = threadCount;
//获得服务器端目标文件的长度
int fileLength = coon.getContentLength();
System.out.println("服务器端文件的大小是:"+fileLength);
//1、在本地弄一个和目标文件一样大的file对象
File descFile=new File("temp.avi");
RandomAccessFile raf=new RandomAccessFile(descFile, "rw");
raf.setLength(fileLength);

//2、计算出每一个线程应该下载文件的大小
blockSize=fileLength/threadCount;

//3、开启若干个线程去下载文件
for (int i = 0; i < threadCount; i++) {
//先设定每个线程下载的开始位置和结束位置
long startIndex=i*blockSize;
long endIndex=(i+1)*blockSize-1;
//如果是最后一个线程
if(i==threadCount-1){
endIndex=fileLength-1;
}
new <span style="color:#ff6666;">DownLoadThread</span>(downloadPath,startIndex,endIndex,i,runningThreadCount,threadCount).start();
}
raf.close();
}

coon.disconnect();
} catch (Exception e) {

e.printStackTrace();
}
}
}

public class <span style="color:#ff6666;">DownLoadThread </span>extends Thread {
private long endIndex;
private long startIndex;
private String urlPath;
private int i;
private static int runningThreadCount;
private int threadCount;

DownLoadThread(String urlPath,long startIndex,long endIndex ,int i,int runningThreadCount,int threadCount){
this.i=i;
this.endIndex=endIndex;
this.startIndex=startIndex;
this.urlPath=urlPath;
this.runningThreadCount=runningThreadCount;
this.threadCount=threadCount;
}

@Override
public void run() {
try {
long totalSize=0,len=0;
//建立positionFile,用于存储每个线程下载的文件字节位置
File positionFile=new File(String.valueOf(i)+".txt");
// 接着从上一次的位置继续下载数据
if (positionFile.exists() && positionFile.length() > 0) {// 判断是否有记录
FileInputStream fis = new FileInputStream(positionFile);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
// 获取当前线程上次下载的总大小是多少
String lasttotalstr = br.readLine();
int lastTotal = Integer.valueOf(lasttotalstr);
System.out.println("上次线程" + i + "下载的总大小:"+ lastTotal);
startIndex += lastTotal;
totalSize += lastTotal;// 加上上次下载的总大小。
fis.close();
}

URL downLoadUrl=new URL(urlPath);
HttpURLConnection connection = (HttpURLConnection) downLoadUrl.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setRequestProperty("Range", "bytes=" + startIndex + "-"+ endIndex);
int code = connection.getResponseCode();
System.out.println("这是第"+i+"个线程,"+"code=" + code);
//下载文件的一部分的返回码是206
if(code==206){
InputStream inputStream = connection.getInputStream();
BufferedInputStream bis=new BufferedInputStream(inputStream);

File tempFile=new File("temp.avi");
RandomAccessFile raf=new RandomAccessFile(tempFile, "rw");
//指定文件开始读取的位置
raf.seek(startIndex);
System.out.println("这是第"+i+"个线程,下载范围是"+startIndex+"~"+endIndex);

byte[] buffer=new byte[1024];
while((len=bis.read(buffer))!=-1){
//这里的模式必须是"rwd"
RandomAccessFile positionRaf=new RandomAccessFile(positionFile, "rwd");
totalSize+=len;
raf.write(buffer, 0, buffer.length);
positionRaf.write(String.valueOf(totalSize).getBytes());
positionRaf.close();
}

bis.close();
raf.close();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// 只有所有的线程都下载完毕后 才可以删除记录文件。
synchronized (DownLoadThread.class) {
System.out.println("线程" + i + "下载完毕了");
--runningThreadCount;
System.out.println(runningThreadCount);
if (runningThreadCount < 1) {
System.out.println("所有的线程都工作完毕了。删除临时记录的文件");
for (int i = 0; i < threadCount; i++) {
File f = new File(i + ".txt");
System.out.println(f.delete());
}
}
}
}
}
}

测试结果:

正常情况下的结果如下图

JAVA多线程断点续传下载

JAVA多线程断点续传下载

如果在下载的过程中,你突然中断的JVM,下次再次下载的时候,控制台输出的结果如下图:

JAVA多线程断点续传下载