在上一节中我们学习了在java中学习多线程下载的基本原理和基本用法,我们并没有讲多线程的断点续传,那么这一节我们就接着上一节来讲断点续传,断点续传的重要性不言而喻,可以不用重复下载,也可以节省时间,实现断点续传的关键在于怎么记录下载的进度和怎么标识,现在我们就来讲一下。
简言之就是:为每个线程开辟一个文件,分别来记录每个线程的下载进度,在每个线程下载之前判断这个标记文件是否存在,如果存在读取相应文件里面的数据,并将下载文件的线程设置到相应的下载点即可。
这一节的代码和上一节其实差不多,仅仅就是多了标记下载进度的一段代码。现在我们一起来看看吧。
这段代码就是在下载线程中去判断每个线程的记录下载进度的文件是否存在,如果存在则读取里面的进度。并把startIndex的值设置到相应的数据。
接着就是上述标记下载进度的文件是怎么生成的,在哪里生成的呢?代码如下:
这个记录文件的生成就是在下载的文件写入到本地的过程中来标记生成的。
最后就是在所有线程结束后,把标记每个线程下载进度的文件删除。记住:实在所有线程结束后一起删除,并不是在每个下载线程运行完后就删除。为什么是所有线程完后再删除呢?自己考虑吧,我就不解释了!
这就是删除标记文件的操作。到这里的解释就完了,懂了吗?
现在我把完整代码贴出来,跟大家共享,代码如下:
package net.loonggg.test; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; public class MutilDownloader { // 开启的线程的个数 public static final int THREAD_COUNT = 3; public static int runningThread = 3;// 记录正在运行的下载文件的线程数 public static void main(String[] args) throws Exception { String path = "文件下载地址"; // 1、连接服务器,获取一个文件,获取文件的长度,在本地创建一个大小跟服务器文件大小一样的临时文件 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("temp.apk", "rwd"); // 指定创建的这个文件的长度 raf.setLength(length); // 关闭raf raf.close(); // 假设是3个线程去下载资源 // 平均每一个线程下载的文件的大小 int blockSize = length / THREAD_COUNT; for (int threadId = 1; threadId <= THREAD_COUNT; threadId++) { // 第一个线程开始下载的位置 int startIndex = (threadId - 1) * blockSize; int endIndex = threadId * blockSize - 1; if (threadId == THREAD_COUNT) { endIndex = length; } System.out.println("----threadId---" + "--startIndex--" + startIndex + "--endIndex--" + endIndex); new DownloadThread(path, threadId, startIndex, endIndex) .start(); } } } /** * 下载文件的子线程,每一个线程下载对应位置的文件 * * @author loonggg * */ public static class DownloadThread extends Thread { private int threadId; private int startIndex; private int endIndex; private String path; /** * @param path * 下载文件在服务器上的路径 * @param threadId * 线程id * @param startIndex * 线程下载的开始位置 * @param endIndex * 线程下载的结束位置 */ public DownloadThread(String path, int threadId, int startIndex, int endIndex) { this.path = path; this.threadId = threadId; this.startIndex = startIndex; this.endIndex = endIndex; } @Override public void run() { try { // 检查是否存在记录下载长度的文件,如果存在读取这个文件的数据 File tempFile = new File(threadId + ".txt"); if (tempFile.exists() && tempFile.length() > 0) { FileInputStream fis = new FileInputStream(tempFile); byte[] temp = new byte[1024 * 10]; int leng = fis.read(temp); // 已经下载的长度 String downloadLen = new String(temp, 0, leng); int downloadInt = Integer.parseInt(downloadLen); startIndex = downloadInt; fis.close(); } URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url .openConnection(); conn.setRequestMethod("GET"); // 重要:请求服务器下载部分的文件 指定文件的位置 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex); conn.setConnectTimeout(5000); // 从服务器请求全部资源的状态码200 ok 如果从服务器请求部分资源的状态码206 ok int code = conn.getResponseCode(); System.out.println("---code---" + code); InputStream is = conn.getInputStream();// 已经设置了请求的位置,返回的是当前位置对应的文件的输入流 RandomAccessFile raf = new RandomAccessFile("temp.apk", "rwd"); // 随机写文件的时候从哪个位置开始写 raf.seek(startIndex);// 定位文件 int len = 0; byte[] buffer = new byte[1024]; int total = 0;// 记录已经下载的数据的长度 while ((len = is.read(buffer)) != -1) { RandomAccessFile recordFile = new RandomAccessFile(threadId + ".txt", "rwd");// 记录每个线程的下载进度,为断点续传做标记 raf.write(buffer, 0, len); total += len; recordFile.write(String.valueOf(startIndex + total) .getBytes()); recordFile.close(); } is.close(); raf.close(); System.out.println("线程:" + threadId + "下载完毕了!"); } catch (Exception e) { e.printStackTrace(); } finally { runningThread--; if (runningThread == 0) {// 所有的线程已经执行完毕 for (int i = 1; i <= THREAD_COUNT; i++) { File file = new File(i + ".txt"); file.delete(); } } } } } }