1.多线程下载原理
(1)本地创建一个大小跟服务器文件大小相同的临时文件
RandomAccessFile raf = new RandomAccessFile("WPS.exe", "rwd");
RandomAccessFile(File file, String mode)
mode的含义
值 | 含意 |
"r" | 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。 |
"rw" | 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 |
"rws" | 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 |
"rwd" | 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。 |
|
|
一般都使用rwd,因为当遇到断电等情况,文件会同步到存储设备上,不会造成数据的掉失。
(2)计算分配几个线程去下载服务器上的资源,知道每个线程下载文件的位置
如上图所示:计算开始位置和结束位置
(3)开启多个线程,每个线程下载对应位置的文件
例子:
package cn.com.download;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class Demo {
public static int threadCount = 3; // 线程的个数
public static void main(String[] args) throws Exception {
// 1 获取服务器,获取一个文件,获取文件的长度,在本地创建一个大小跟服务器文件一样大的临时文件
String path = "http://192.168.1.100:8080/WEB/WPS.exe";// 下载路径 这里是web项目中的
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
if (code == 200) {
int length = conn.getContentLength();
System.out.println("文件总长度" + length);
// 在客户端本地创建一个大小跟服务器端资源一样大小的临时文件
RandomAccessFile raf = new RandomAccessFile("WPS.exe", "rwd");
// 指定创建这个文件的长度
raf.setLength(length);
raf.close();
// 假设是3个线程下载资源
// 平均每一个线程下载文件的大小
int blockSize = length / threadCount;
// 设置每一个线程的起始和末尾的位置
for (int threadId = 1; threadId <= threadCount; threadId++) {
// 设置线程下载的开始位置
int startIndex = (threadId - 1) * blockSize;
int endIndex = threadId * blockSize - 1;
if (threadId == threadCount) {// 最后一个线程下载的长度不一样,用公式得不到。但是确定的是endindex=length
endIndex = length;
}
System.out.println("线程:" + threadId + "下载:----" + startIndex
+ "---->" + endIndex);
// 在主线程中调用子线程,开始下载
new DownLoadThread(threadId, startIndex, endIndex, path)
.start();
}
} else {
System.out.println("服务器错误");
}
}
/**
* 下载资源的子线程,每个线程下载对应的对应的资源
*
* @author olay
*
*/
public static class DownLoadThread extends Thread {
private int threadId, startIndex, endIndex;
private String path;
/**
*
* @param threadId
* 线程id
* @param startIndex
* 线程下载的开始位置
* @param endIndex
* 线程下载的结束位置
* @param 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() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
// 重要:请求服务器下载部分的文件 指定文件的下载位置
conn.setRequestProperty("Range", "bytes-" + startIndex + "-"
+ endIndex);
int code = conn.getResponseCode();// 从服务器请求全部资源成功的响应码是200,而从服务器请求部分资源成功的响应码是206
InputStream is = conn.getInputStream();// 返回当前位置对应的文件的输入流
RandomAccessFile raf = new RandomAccessFile("WPS.exe", "rwd");
// 随机写文件的时候从哪个位置开始写
raf.seek(startIndex);// 定位文件
// 写入内存
int len = 0;
byte[] buffer = new byte[1024];// 声明缓冲区
while ((len = is.read(buffer)) != -1) {
raf.write(buffer, 0, len);// 写入到临时文件
}
is.close();
raf.close();
System.out.println("线程" + threadId + "下载完毕...");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
执行结果
二.多线程断点下载
多线程断点下载就是:把一个下载资源分N份(N条线程)下载,当下载中遇到特殊情况(如断点)停止下载(停止下载时,程序会保存已经下载了的资源),那么当你再次下载时资源不会重新下载,而是在之前下载的基础下继续下载。
例子:分了3条线程下载一个资源,并且新建了三个资源文件来记录每条线程的下载记录
最后所以下载完成后,记录线程的文件会被删除
package cn.com.download;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class Demo {
public static int threadCount = 3; // 线程的个数
public static int runningThread = threadCount = 3;// 线程的个数,用于删除记录下载长度的各个文件
public static void main(String[] args) throws Exception {
// 1 获取服务器,获取一个文件,获取文件的长度,在本地创建一个大小跟服务器文件一样大的临时文件
String path = "http://192.168.1.102:8080/WEB/olay.avi";// 下载路径
// 这里是web项目中的
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
if (code == 200) {
int length = conn.getContentLength();
System.out.println("文件总长度" + length);
// 在客户端本地创建一个大小跟服务器端资源一样大小的临时文件
RandomAccessFile raf = new RandomAccessFile("olay.avi", "rwd");
// 指定创建这个文件的长度
raf.setLength(length);
raf.close();
// 假设是3个线程下载资源
// 平均每一个线程下载文件的大小
int blockSize = length / threadCount;
// 设置每一个线程的起始和末尾的位置
for (int threadId = 1; threadId <= threadCount; threadId++) {
// 设置线程下载的开始位置
int startIndex = (threadId - 1) * blockSize;
int endIndex = threadId * blockSize - 1;
if (threadId == threadCount) {// 最后一个线程下载的长度不一样,用公式得不到。但是确定的是endindex=length
endIndex = length;
}
System.out.println("线程:" + threadId + "下载:----" + startIndex
+ "---->" + endIndex);
// 在主线程中调用子线程,开始下载
new DownLoadThread(threadId, startIndex, endIndex, path)
.start();
}
} else {
System.out.println("服务器错误");
}
}
/**
* 下载资源的子线程,每个线程下载对应的对应的资源
*
* @author olay
*
*/
public static class DownLoadThread extends Thread {
private int threadId, startIndex, endIndex;
private String path;
/**
*
* @param threadId
* 线程id
* @param startIndex
* 线程下载的开始位置
* @param endIndex
* 线程下载的结束位置
* @param 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() {
try {
// 检查是否存在记录下载长度的文件,如果存在就读取这个文件的数据
File tempFile = new File(threadId + ".txt");// 创建仔资源的临时文件
long i = tempFile.length();
if (tempFile.exists() && tempFile.length() > 0) {// 临时文件存在并且不为空
FileInputStream fis = new FileInputStream(tempFile);// 创建文件输入流
byte[] temp = new byte[1024];// 创建缓冲区
int leng = fis.read(temp);// 读取到文件输入流中
String downloadlenLong = new String(temp, 0, leng);
int downloadlenInt = Integer.valueOf(downloadlenLong);
startIndex = downloadlenInt;// 下载后突然断开,第二次下载开始位置为第一次下载完后继续下载
fis.close();
} else {
System.out.println("文件不存在");
}
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
// 重要:请求服务器下载部分的文件 指定文件的下载位置
conn.setRequestProperty("Range", "bytes-" + startIndex + "-"
+ endIndex);
int code = conn.getResponseCode();// 从服务器请求全部资源成功的响应码是200,而从服务器请求部分资源成功的响应码是206
InputStream is = conn.getInputStream();// 返回当前位置对应的文件的输入流
RandomAccessFile raf = new RandomAccessFile("olay.avi", "rwd");
// 随机写文件的时候从哪个位置开始写
raf.seek(startIndex);// 定位文件
// 写入内存
int len = 0;
byte[] buffer = new byte[1024];// 声明缓冲区
int total = 0; // 已经下载的数据长度
while ((len = is.read(buffer)) != -1) {
raf.write(buffer, 0, len);// 写入到临时文件
total += len;
System.out.println("线程" + threadId + "total:" + total);
RandomAccessFile file = new RandomAccessFile(threadId
+ ".txt", "rwd");// 记录当前下载记录的长度
file.write((total + startIndex + "").getBytes());// 帮已经下载的字节写到对应的file文件中
file.close(); // 记得关RandomAccessFile,不然删除不了文件
}
is.close();
raf.close();
System.out.println("线程" + threadId + "下载完毕...");
} catch (Exception e) {
e.printStackTrace();
} finally {
runningThread--;
if (runningThread == 0) { // 全部线程下载完成
System.out.println("开始删除记录文件");
for (int i = 1; i <= 3; i++) {
File file = new File(i + ".txt");
if (file.exists()) {
System.out.println(i + ".txt文件存在");
if (file.delete()) {
System.out.println("删除" + i + ".txt文件成功");
}
}
}
}
}
}
}
}
下载过程中,生成三个文件记录每条线程的下载记录
全部下载完成后,删除记录文件