文件下载这种操作经常会出现,当文件较大的时候,假如不采用多线程断点下载,那么当下载到一半出错或者暂停的话,就需要从最开始重新来下载了,这点是没什么必要的,因为我们完全可以从上一次出错的地方继续下载,就像迅雷一样,我们下载文件的时候,不可能中途出现错误再次开始的时候从整个文件的开始下载,如果你平时下载细致的话会发现迅雷下载的过程中都有一个临时文件的存在,这个临时文件存放的就是你已经下载到文件的哪个字节啊,下载时间啊之类的信息以便于你暂停或者出错之后能够从断点继续下载;
首先讲解下实现思路,随后附上代码:
(1):首先获取并打开一个http连接;
(2):根据服务器端的返回,获取到文件的大小,并且在本地通过RandomAccessFile建立一个可读可写的文件,大小就是刚刚计算出来的文件大小,这种类型文件的最大好处就是可以定位到文件中的某一个字节处,这样我们采用多线程下载的时候即使上一个线程没有下载结束的话,下一个线程照样可以下载自己的,因为他们分担下载的字节区间是不一样的;
(3):计算出每个线程需要下载的字节范围,最后一个线程比较特殊,因为他可能不会正好是下载length / threadNum 大小的字节数;
(4):开启线程进行下载,run方法首先会去查看有没有当前线程名字的临时文件存在,有的话读出临时文件中的数据,这个数据就是当前线程之前已经下载过的字节数,那么我们下次在进行下载的时候就没必要从当前线程应该开始的字节处啦,直接从startIndex加上临时文件的字节处开始就可以了,也就是这样保证了断点下载(注意一点的就是每个线程都有一个临时文件存在,其存储的是当前线程已经下载的字节数);
(5)接下来就是利用http的RANGE字段来设置当前线程将要下载的开始和结束字节,随后获取到输入流,将该流中的内容写入到文件中;
(6)最后当当前线程执行结束时,删除临时文件;
代码如下:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class MultiDownload {
public static String path = "http://localhost:8080/1.apk";//下载文件的路径
public static String store = "d:/1.apk";//文件存储路径
public static String temp = "d:/";//临时文件的存储路径
public static String method = "GET";
public static int threadNum = 2;//设置开启的线程数量
public static void main(String[] args) {
HttpURLConnection connection = getConnection(path,method);
try {
connection.connect();//打开连接
if(connection.getResponseCode() == 200)
{
//获取文件的大小
int length = connection.getContentLength();
System.out.println(length);
File file = new File(store);
//生成临时文件
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//设置临时文件的大小
raf.setLength(length);
int size = length/threadNum;
for(int i = 0;i < threadNum;i++)
{
int startIndex = i*size;
int endIndex = (i+1)*size-1;
if(i == threadNum-1)
endIndex = length-1;
System.out.println("start: "+startIndex+" end: "+endIndex);
new DownloadThread(startIndex, endIndex, i, raf).start();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取连接
* @param path
* @return
*/
public static HttpURLConnection getConnection(String path,String method)
{
HttpURLConnection connection = null;
try {
URL url = new URL(path);
connection = (HttpURLConnection) url.openConnection();
connection.setReadTimeout(6000);//设置读取超时时间
connection.setConnectTimeout(6000);//设置连接超时时间
connection.setRequestMethod(method);
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
}
class DownloadThread extends Thread
{
public int startIndex;
public int endIndex;
public int threadId;
public RandomAccessFile raf;
public DownloadThread(int startIndex, int endIndex, int threadId, RandomAccessFile raf) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
this.raf = raf;
}
@Override
public void run() {
//首先先到临时文件中查看是否有已经下载的字节数记录
File progressFile = new File(MultiDownload.temp+threadId + ".txt");
if(progressFile.exists())
{
try {
BufferedReader reader = new BufferedReader(new FileReader(progressFile));
startIndex = startIndex+Integer.parseInt(reader.readLine());//计算出断点需要开始下载的字节位置
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
HttpURLConnection connection = MultiDownload.getConnection(MultiDownload.path,MultiDownload.method);
connection.setRequestProperty("RANGE", "bytes="+startIndex+"-"+endIndex);
try {
connection.connect();
raf.seek(startIndex);
if(connection.getResponseCode() == 206)
{
InputStream is = connection.getInputStream();
byte[] buffer = new byte[2048];
int len = 0;
int total = 0;
while((len = is.read(buffer)) != -1)
{
raf.write(buffer, 0, len);
total += len;
//将执行进度写入临时文件中
RandomAccessFile tempRaf = new RandomAccessFile(progressFile, "rwd");
tempRaf.write((total+"").getBytes());
tempRaf.close();
}
}
//只要线程执行到这一步的话,就已经表明他已经完成了自己应该爬取的字节范围啦,所以要删除临时文件
progressFile.delete();
} catch (Exception e) {
e.printStackTrace();
}
}
}
中间的代码有参考师兄的,多谢师兄的指导啦!