Java实现多线程下载和断点续传

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

java的多线程下载能够明显提升下载的速度,平时我们用的迅雷软件之所以能够下载那么快,就是使用了多线程;当用户在下载的过程中,有断电或断网的可能,当用户再次点击下载时,应该让用户接着原来的进度进行下载,这可以节约用户的流量,所以要用到断点续传的功能。下面是通过Java代码实现多线程下载和断点续传的详细代码。

1,创建一个类,用于文件的下载

<pre name="code" class="java"><span style="font-size:18px;">package com.edu.thread;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

public class MultiDownload2 {
static String path = "http://localhost:8080/wlan.zip";
//开启线程的数量
static int threadCount = 6;
//下载结束的线程数
static int threadFinished = 0;
public static void main(String[] args) {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
//此时只是确定和服务器建立了连接,但并没有开始下载任务
if (conn.getResponseCode()==200) {
//拿到文件的长度
int length = conn.getContentLength();
//指定文件路径和文件名
File file = new File("d://文件测试", getFileName(path));
//创建随机存储文件大小,为了建立一个和源文件大小相同的存储区间
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//设置临时文件的大小,和服务器文件一模一样
raf.setLength(length);
//计算每个线程下载的字节数
int size = length / threadCount;
//计算三个线程下载的开始位置和结束位置
for (int i = 0; i < threadCount; i++) {
int startIndex = i * size;
int endIndex = (i + 1) * size-1;
//如果是最后一个线程,要把结尾读完
if (i == threadCount-1) {
//length从0开始读,所以length-1表示最后一个字节
endIndex = length-1;
}
//打印三个线程的开始与结束位置
System.out.println("线程"+i+"的开始和结束位置:"+startIndex+"----"+endIndex);
//开启线程,传入线程ID,下载的开始位置和下载的结束位置
new DownloadThread(i, startIndex, endIndex).start();;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* 获取文件名
*/
public static String getFileName(String path){
int index=path.lastIndexOf("/");
return path.substring(index + 1);
}
}</span>
 

2,创建另一个类,用于开启子线程

<pre name="code" class="java"><span style="font-size:18px;">package com.edu.thread;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
//新开启一个线程,用于完成下载任务
class DownloadThread extends Thread{

int thredId;
int startIndex;
int endIndex;

public DownloadThread(int thredId, int startIndex, int endIndex) {
super();
this.thredId = thredId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}

public void run() {
try {
//下载进度文件保存的路径和文件名
File progressFile = new File("d://文件测试",(thredId + ".txt"));
//判断保存下载进度的临时文件是否存在,以便确定下载的开始位置
if (progressFile.exists()) {
FileInputStream fis = new FileInputStream(progressFile);
BufferedReader bReader = new BufferedReader(new InputStreamReader(fis));
//拿到临时文件中保存的数据,并把此数据设置为新的开始位置
int text = Integer.parseInt(bReader.readLine());
startIndex = text;
fis.close();
}
System.out.println("线程"+thredId+"的最终开始下载位置是:"+startIndex);

URL url = new URL(MultiDownload2.path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
//设置请求数据的范围
conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
//建立连接,状态码206表示请求部分数据成功,此时开始下载任务
if (conn.getResponseCode()==206) {
InputStream is = conn.getInputStream();
//指定文件名和文件路径
File file = new File(MultiDownload2.getFileName(MultiDownload2.path) );
int len = 0;
byte [] b = new byte[1024];
//三个线程各自创建自己的随机存储文件
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//设置数据从哪个位置开始写入数据到临时文件
raf.seek(startIndex);
//设置当前线程下载的总字节数
int total = 0;
long start = System.currentTimeMillis();

//当下载意外停止时,记录当前下载进度
int currentPosition = startIndex;

while ((len=is.read(b))!=-1) {
raf.write(b,0,len);
//打印当前线程下载的总字节数
total += len;
/**
* 实现断点续传的功能
*/
//RandomAccessFile主要用来存放下载的临时文件,可以用FileOutputStream代替
RandomAccessFile rafProgress = new RandomAccessFile(progressFile, "rwd");
//再次下载时的开始位置
currentPosition = startIndex + total;
//把下载进度写进rafProgress临时文件,下一次下载时,就以这个值作为新的startIndex
rafProgress.write((currentPosition + "").getBytes());
//关流
rafProgress.close();
System.out.println("线程"+thredId+"已经下载了"+total);
}
raf.close();
long end = System.currentTimeMillis();
//打印线程下载文件用时
System.out.println("线程"+thredId+"下载文件用时"+(end-start)+"ms");
//打印线程的结束
System.out.println("线程:"+thredId+" 下载结束了 !!!");
//下载结束后,删除所有的临时文件
MultiDownload2.threadFinished ++;
//使用同步语句块,保证线程的安全性
synchronized (MultiDownload2.path) {
//如果这个条件成立,说明所有的线程下载结束
if (MultiDownload2.threadFinished == MultiDownload2.threadCount) {
for (int i = 0; i < MultiDownload2.threadCount; i++) {
//删除三个线程产生的临时文件
File temp = new File("d://文件测试", i + ".txt");
temp.delete();
}
//保证三个线程的临时文件同时被删除
MultiDownload2.threadFinished = 0;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}</span>