理论基础:
1.从服务器获取文件大小,本地创建一个和服务器一样大的临时文件
2. 计算分配几个线程去下载服务器上资源,每个线程知道写入本地文件位置,RandomAccessFile
100M 文件
线程1下载位置 0-33M, 写入位置 0
线程2下载位置 33-66M , 写入位置33M
线程3下载位置 66—100M , 写入位置 66 M
3. 开启多个3个线程,每一个线程从对应位置开始下载写入到本地位置,同时创建临时文件
4. 下载中断,开始下载,读取临时文件大小,根据临时文件设置range头
4. 所有线程下载完毕,下载完毕,RandomAccessFile 写入完毕,删除临时文件
注意点:
在HTTP协议中可以通过Range头字段指定每条线程从文件什么位置开始下载,
下载到什么位置即可,
比如指定从文件2M位置开始下载, 下载到位置(4M-1 byte)为止
RandomAccessFile 可以指定写入起始位置
如何实现断点下载:
为了实现断点下载,需要创建临时文件,第二次下载的时候读取临时文件大小
====================================================================================
package download3;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class MultiDownloader {
// 线程的数量
private static int threadCount = 3;
// 正在运行的线程的个数
private static int runningThreadCount ;
public static void main(String[] args) throws Exception {
//采用多个线程把服务器的资源下载下来
String path = "http://192.168.168.157:8080/qq.txt";//推荐使用exe文件
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
int code = conn.getResponseCode();
if(code == 200){
int length = conn.getContentLength();
System.out.println("服务器文件的大小为:"+length);
//1. 确定文件大小,在本地创建一个大小和服务器一模一样的空文件。RandomAccessFile
RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
raf.setLength(length);
raf.close();
conn.disconnect(); // 断开http
//2.等份服务器的资源为若干份,让每个子线程下载自己的部分。
int blocksize = length/threadCount;
runningThreadCount = threadCount;
for(int threadid =0;threadid<threadCount;threadid++){
int startIndex = threadid*blocksize;
int endIndex = (threadid+1)*(blocksize)-1;
//特殊情况
if(threadid==(threadCount-1)){
//最后一个线程。结束位置为文件总长度-1
endIndex = length -1;
}
// 3. 启动线程开始下载
new DownloadThread(threadid, startIndex, endIndex, path).start();
}
}
}
// 下载资源的线程
public static class DownloadThread extends Thread{
private int threadid;
private int startindex;
private int endindex;
private String path;
public DownloadThread(int threadid, int startindex, int endindex,
String path) {
this.threadid = threadid;
this.startindex = startindex;
this.endindex = endindex;
this.path = path;
}
@Override
public void run() {
System.out.println("线程id:"+threadid+"理论下载位置:"+startindex+"~"+endindex);
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
//查看文件里面是否记录有当前线程的下载开始位置,实现断点下载
File infofile = new File(threadCount+getFileName(path)+threadid+".txt");
if(infofile.exists()&&infofile.length()>0){
FileInputStream fis = new FileInputStream(infofile);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
String newstartindex = br.readLine();
//如果本地文件存在计算开始下载位置
conn.setRequestProperty("Range", "bytes="+newstartindex+"-"+endindex);
// System.out.println("线程id:"+threadid+"真实的下载位置:"+newstartindex+"~"+endindex);
startindex = Integer.parseInt(newstartindex);
fis.close();//记得释放文件的引用
}else{
//4. 不是下载整个文件,而是下载文件的一部分。 告诉服务器 下载的资源就是一部分
//HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303");
conn.setRequestProperty("Range", "bytes="+startindex+"-"+endindex);
System.out.println("线程id:"+threadid+"真实的下载位置:"+startindex+"~"+endindex);
}
int code = conn.getResponseCode();
//200 OK 206 请求部分数据成功,下载部分文件服务器返回206
System.out.println("返回状态码:code"+ code );
if(code == 206){
//当前的线程 就应用下载这一部分的数据。开始下载数据
InputStream is = conn.getInputStream();
RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
raf.seek(startindex);
//注意:不同线程存文件的开始位置是不相同的,要从自己对应的位置存文件
byte[] buffer = new byte[1024*4];
int len = -1;
int total = 0;//代表当前线程下载的总大小
while(( len = is.read(buffer))!=-1){
// 写入 本地创建的文件
raf.write(buffer, 0, len);
total+=len;
int currentposition = startindex+total;//当前线程下载的位置。
System.err.println("线程id:"+threadid+ " 下载到:"+ currentposition);
// 为了确保断点下载,写入临时文件数据
File file = new File(threadCount+getFileName(path)+threadid+".txt");
//确保了每次循环都会把进度写到底层存储设备里面。
/** r: 文件只读方式打开
* rw: 文件读写方式打开
* rws: 文件读写方式打开,对文件内容或者元数据的每个更新都同步到写入底层设备
rwd:文件读写方式打开, 对文件内容的每个更新都写入到底层设备,断电了数据也立刻写入
**/
RandomAccessFile rafos = new RandomAccessFile(file, "rwd");
//把当前线程的位置信息写入到一个文件里面
rafos.write(String.valueOf(currentposition).getBytes());
rafos.close();//数据并不是直接保存到底层的存储设备里面,保存到缓存,缓存空间满了,数据会同步到底层设备。
}
is.close();
raf.close();
System.out.println("线程:"+threadid+"下载完毕了。");
synchronized (MultiDownloader.class) {
runningThreadCount--;
if(runningThreadCount<=0){
System.out.println("线程全部下载完毕了。");
for(int i=0;i<threadCount;i++){
File f = new File(threadCount+getFileName(path)+i+".txt");
// 删除临时文件
f.delete();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 获取文件名称
* @param path
* @return
*/
public static String getFileName(String path){
int startindex = path.lastIndexOf("/")+1;
return path.substring(startindex);
}
}
====================================================================================
完整源码:
tomcat配置路径:D:\web\Apache Tomcat 7.0.41\webapps\ROOT\qq.txt 启动tomcat
package download3;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* 多线程的下载器 javase代码
*
*/
public class MultiDownloader {
/**
* 线程的数量
*/
private static int threadCount = 3;
/**
* 正在运行的线程的个数
*/
private static int runningThreadCount ;
public static void main(String[] args) throws Exception {
//采用多个线程把服务器的资源下载下来。
String path = "http://192.168.168.157:8080/qq.txt";//推荐使用exe文件
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
int code = conn.getResponseCode();
if(code == 200){
int length = conn.getContentLength();
System.out.println("服务器文件的大小为:"+length);
//1.在本地创建一个大小和服务器一模一样的空文件。RandomAccessFile
RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
raf.setLength(length);
raf.close();
conn.disconnect(); // 断开http
//2.等份服务器的资源为若干份,让每个子线程下载自己的部分。
int blocksize = length/threadCount;
runningThreadCount = threadCount;
for(int threadid =0;threadid<threadCount;threadid++){
int startIndex = threadid*blocksize;
int endIndex = (threadid+1)*(blocksize)-1;
//特殊情况
if(threadid==(threadCount-1)){
//最后一个线程。结束位置为文件总长度-1
endIndex = length -1;
}
new DownloadThread(threadid, startIndex, endIndex, path).start();
}
}
}
/**
* 下载资源的线程
*
*/
public static class DownloadThread extends Thread{
private int threadid;
private int startindex;
private int endindex;
private String path;
public DownloadThread(int threadid, int startindex, int endindex,
String path) {
this.threadid = threadid;
this.startindex = startindex;
this.endindex = endindex;
this.path = path;
}
@Override
public void run() {
System.out.println("线程id:"+threadid+"理论下载位置:"+startindex+"~"+endindex);
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
//查看文件里面是否记录有当前线程的下载开始位置。
File infofile = new File(threadCount+getFileName(path)+threadid+".txt");
if(infofile.exists()&&infofile.length()>0){
FileInputStream fis = new FileInputStream(infofile);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
String newstartindex = br.readLine();
conn.setRequestProperty("Range", "bytes="+newstartindex+"-"+endindex);
// System.out.println("线程id:"+threadid+"真实的下载位置:"+newstartindex+"~"+endindex);
startindex = Integer.parseInt(newstartindex);
fis.close();//记得释放文件的引用
}else{
//不是下载整个文件,而是下载文件的一部分。 告诉服务器 下载的资源就是一部分
//HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303");
conn.setRequestProperty("Range", "bytes="+startindex+"-"+endindex);
System.out.println("线程id:"+threadid+"真实的下载位置:"+startindex+"~"+endindex);
}
int code = conn.getResponseCode();//200 OK 206 请求部分数据成功
System.out.println("返回状态码:code"+ code );
if(code == 206){
//当前的线程 就应用下载这一部分的数据。
InputStream is = conn.getInputStream();
RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
raf.seek(startindex);//注意:不同线程存文件的开始位置是不相同的,要从自己对应的位置存文件
byte[] buffer = new byte[1024*4];
int len = -1;
int total = 0;//代表当前线程下载的总大小
while(( len = is.read(buffer))!=-1){
raf.write(buffer, 0, len);
total+=len;
int currentposition = startindex+total;//当前线程下载的位置。
System.err.println("线程id:"+threadid+ " 下载到:"+ currentposition);
File file = new File(threadCount+getFileName(path)+threadid+".txt");
//确保了每次循环都会把进度写到底层存储设备里面。
/** r: 文件只读方式打开
* rw: 文件读写方式打开
* rws: 文件读写方式打开,对文件内容或者元数据的每个更新都同步到写入底层设备
rwd:文件读写方式打开, 对文件内容的每个更新都写入到底层设备,断电了数据也立刻写入
**/
RandomAccessFile rafos = new RandomAccessFile(file, "rwd");
//把当前线程的位置信息写入到一个文件里面
rafos.write(String.valueOf(currentposition).getBytes());
rafos.close();//数据并不是直接保存到底层的存储设备里面,保存到缓存,缓存空间满了,数据会同步到底层设备。
}
is.close();
raf.close();
System.out.println("线程:"+threadid+"下载完毕了。");
synchronized (MultiDownloader.class) {
runningThreadCount--;
if(runningThreadCount<=0){
System.out.println("线程全部下载完毕了。");
for(int i=0;i<threadCount;i++){
File f = new File(threadCount+getFileName(path)+i+".txt");
f.delete();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 获取文件名称
* @param path
* @return
*/
public static String getFileName(String path){
int startindex = path.lastIndexOf("/")+1;
return path.substring(startindex);
}
}