建议读者在看之前先独立思考一番,以下为个人实现方案
思路图
编码实现
JavaSE:
public class BreakpointDownloader {
private static final int THREAD_COUNT = 3;
private static final String LOCAL_SAVE_FILENAME = "setup.exe";
private static volatile int runningThreadCount = THREAD_COUNT;
public static void main(String[] args) {
String remoteFilePath = "http://www.voidtools.com/Everything-1.4.1.895.x86-Setup.exe";
try {
// 1. 获取当前服务器上文件的大小
URL url = new URL(remoteFilePath);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setConnectTimeout(10000);
httpConn.setRequestMethod("GET");
if (httpConn.getResponseCode() != 200) {
System.out.println("服务器返回的状态码是 :" + httpConn.getResponseCode());
return;
}
int remoteFileLength = httpConn.getContentLength();
System.out.println("下载的文件大小是:" + remoteFileLength);
// 2. 创建一个和服务器大小一致的本地文件 用来存放下载的文件
// 随机访问文件写入
// 其中第二个参数 中的mode rwd 和 rws 区别是:rws 要求对文件的内容或元数据的每个更新都同步写入到底层存储设备 ,元数据是指 文件的一些摘要、作者、日期等信息,我们暂用不到
// bt毁硬盘,因为会实时更新到存储设备,如果不指定rwd 或者rws 默认会先写入缓存区 这样就不保证断电等一些硬件设备异常数据不能及时保存到硬盘中
RandomAccessFile storeFileRaf = new RandomAccessFile(LOCAL_SAVE_FILENAME, "rwd");
storeFileRaf.setLength(remoteFileLength);
storeFileRaf.close();
// 3. 确定每一个线程下载的block
int averageBlockLen = remoteFileLength / THREAD_COUNT;
// 4. 确定每一个线程下载的区间
for (int i = 1; i <= THREAD_COUNT; i++) {
int startIndex = (i - 1) * averageBlockLen;
int endIndex = i * averageBlockLen - 1;
if (i == THREAD_COUNT) {
// 最后一个线程特殊一点,剩余的全部交付给他执行下载
endIndex = remoteFileLength;
}
System.out.println("线程" + i + "首次分配的 指针分别是:" + startIndex + "-------->" + endIndex);
// 5. 开启线程 下载文件
new DownloadFileThread(startIndex, endIndex, remoteFilePath, i + "").start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static class DownloadFileThread extends Thread {
private int startIndex;
private int endIndex;
private String path;
private String threadName;
public DownloadFileThread(int startIndex, int endIndex, String path, String threadName) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.path = path;
this.threadName = threadName;
}
@Override
public void run() {
URL url = null;
try {
url = new URL(path);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setConnectTimeout(10000);
httpConn.setRequestMethod("GET");
File recordTmpFile = new File(threadName + ".txt");
if (recordTmpFile.exists() && recordTmpFile.length() > 0) {
// 说明上次下载到某一个位置就停止了,本次下载从下次的位置开始进行
FileInputStream fis = new FileInputStream(recordTmpFile);
byte[] buffer = new byte[1024];
int read = fis.read(buffer); // 读取到buffer了
String res = new String(buffer, 0, read);
startIndex = Integer.parseInt(res);
fis.close();
}
System.out.println("线程" + threadName + "真实下载位置: statIndex:" + startIndex + "------->" + "endIndex = " + endIndex);
// 确定当前这个线程下载指定文件block ,非常重要
httpConn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
System.out.println("指定线程获取server return code = " + httpConn.getResponseCode());
// 从服务器上获取全部资源返回 200, 部分资源返回206
if (httpConn.getResponseCode() / 200 != 1) {
return;
}
InputStream inputStream = httpConn.getInputStream();// 如果不指定Range , 这个获取的输入流肯定是整个文件的输入流
// 输入流写入到指定位置
RandomAccessFile targetSaveRaf = new RandomAccessFile(LOCAL_SAVE_FILENAME, "rwd");
targetSaveRaf.seek(startIndex);
byte[] buffer = new byte[1024];
int readLen;
int totalLen = 0;
File recordFile = new File(threadName + ".txt");
if (!recordFile.exists()) {
recordFile.createNewFile();
}
RandomAccessFile recordProgressRaf = null;
while ((readLen = inputStream.read(buffer)) != -1) {
targetSaveRaf.write(buffer, 0, readLen);
totalLen += readLen;
// 每一次实例化一个 raf, 每一次都覆盖上一次的值,保证实时更新到本地文件中
recordProgressRaf = new RandomAccessFile(recordFile, "rwd");
recordProgressRaf.write(String.valueOf(startIndex + totalLen).getBytes());
recordProgressRaf.close();
}
inputStream.close();
targetSaveRaf.close();
System.out.println("线程" + threadName + "下载完毕!!!");
runningThreadCount--;
} catch (Exception e) {
e.printStackTrace();
System.out.println("线程" + threadName + "下载失败: " + e.getMessage());
} finally {
if (runningThreadCount == 0) {
// 所有线程都下载完毕且成功了 删除进度记忆文件
for (int i = 1; i <= THREAD_COUNT; i++) {
File file = new File(i + ".txt");
boolean delete = file.delete();
if (delete) {
System.out.println("记录文件 " + i + ".txt 删除成功");
} else {
System.out.println("记录文件 " + i + ".txt 删除失败");
}
}
}
}
}
}
}
复制代码