多线程断点下载原理:
1.设置Range请求头,开启子线程后,各归各下载相应的数据
2.下载的过程中,每个线程都有个文件实时记录当前下载进度,保证暂停下载后再次继续可以获取上次下载的进度。
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;
/**
*
* @author 多线程断点下载
*
*/
public class ManyThreadDownload {
private static int threadTotalCount = 3; //总共开几个线程运行
private static String path = "http://127.0.0.1:9090/555.exe"; //下载地址
private static int runningThreadCount; //当前正在运行的线程个数
public static void main(String[] args) {
HttpURLConnection conn = null;
try {
URL url = new URL(path); //URL地址
conn = (HttpURLConnection) url.openConnection(); //获取一个连接对象
conn.setConnectTimeout(3000); //设置连接超时时间
conn.setReadTimeout(3000); //设置已经连上服务器,但是在读取数据时候的超时时间
conn.setRequestMethod("GET"); //设置请求方式
if (conn.getResponseCode() == 200) { //请求成功后
int size = conn.getContentLength(); //获取服务器返回的文件大小
long blockSize = size / threadTotalCount; //每个线程要下载的大小=服务器文件大小/线程数
runningThreadCount = threadTotalCount; //刚开始的时候将线程总数赋值给正在运行的线程数
for (int i = 0; i < threadTotalCount; i++) { //循环开启线程下载
long startIndex = i * blockSize; //计算每个线程开始位置下载的位置
long endIndex = (i + 1) * blockSize - 1; //计算每个线程结束位置下载的位置
if (i == (threadTotalCount - 1)) { //当循环到最后一个线程的时候,下载的结束位置就是文件大总大小
endIndex = size;
}
new DownLoadThread(i, path, startIndex, endIndex).start();//进行下载
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
private static class DownLoadThread extends Thread {
private int threadIndex; //当前线程索引号
private String path; //下载文件的url
private long startIndex; //开始下载的位置
private long endIndex; //下载到哪个位置结束
public DownLoadThread(int threadIndex, String path, long startIndex, long endIndex) {
this.threadIndex = threadIndex;
this.path = path;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void run() {
super.run();
HttpURLConnection conn = null;
File currentPositionFile = new File("Thread"+threadIndex+".txt"); //这个文件是用于记录每个线程下载进度的一个文件,只保存下载的字节数
/* RandomAccessFile
* 用这个类来操作保存下载进度的文件,因为将这个类第二个参数设置rwd模式的话,会实时更新硬盘上的数据,而如果用FileOutputStream的话,就算调用flush,也只是刷到硬盘上的缓存里,
* 并没有马上写到硬盘上
*/
RandomAccessFile currentPositionRAF = null;
int total = 0; //当前下载的字节进度
try {
if(currentPositionFile.exists()&¤tPositionFile.length()>0){ //如果记录进度的文件存在的话,就表示之前暂停过
FileInputStream fis = new FileInputStream(currentPositionFile);
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
String lastPosition = br.readLine();
if(lastPosition!=null&&!"".equals(lastPosition.trim())){ //读取进度文件,如果读到的内容不为空
total = Integer.parseInt(lastPosition); //将当前线程的总进度设置为进度文件里的进度
startIndex += Integer.valueOf(lastPosition); //将当前开始进度设置为进度文件里的进度(因为是断点下载)
System.out.println("线程"+threadIndex+",从"+lastPosition+"继续开始");
}
fis.close(); //千万不要忘记关闭流
isr.close(); //千万不要忘记关闭流
br.close(); //千万不要忘记关闭流
}
URL url = new URL(path); //URL地址
conn = (HttpURLConnection) url.openConnection(); //获取一个连接对象
conn.setConnectTimeout(3000); //设置连接超时时间
conn.setReadTimeout(3000); //设置已经连上服务器,但是在读取数据时候的超时时间
conn.setRequestMethod("GET"); //设置请求方式
conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex); //设置消息头是断点下载,告诉服务器返回指定片段数据
if(conn.getResponseCode()==206){ //带着Range消息头去访问服务器,服务器返回的状态码是206
InputStream is = conn.getInputStream(); //获取服务器返回数据的输入流
File file = new File("hehe.exe"); //创建本地文件
RandomAccessFile raf = new RandomAccessFile(file, "rw"); //创建一个往下载文件里写数据的类
raf.seek(startIndex); //设置上次的进度
int len = 0;
byte[] buffer = new byte[1024*1024];
while((len=is.read(buffer))!=-1){
total += len;
currentPositionRAF = new RandomAccessFile(currentPositionFile, "rwd");
currentPositionRAF.write(String.valueOf(total).getBytes()); //写进度
raf.write(buffer, 0, len);
//记录文件下载进度的txt文件必须是循环里写一次关一次,因为JVM关闭的时候,不一定是循环完毕的时候,这时候进度文件还是打开着的
currentPositionRAF.close();
}
is.close();
raf.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
synchronized (ManyThreadDownload.class) {
if(conn!=null){
conn.disconnect();
}
//因为执行到finally时候,肯定是进程已经下载完毕了,所以把当前在运行的进程数减一
runningThreadCount--;
if(runningThreadCount==0){
for (int i = 0; i < threadTotalCount; i++) { //删除记录每个线程进度的临时文件
File file = new File("Thread"+i+".txt");
try {
System.out.println(file.delete());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
}
}