Java多线程断点下载文件
-
Java实现断点续传+多线程下载
如下代码所示,每一步都有注解
思路:
- 通过URL连接到服务器上要下载的文件,得到文件的大小;
- 算出每条线程下载的开始位置和结束位置,例如,有两条线程下载100Byte的文件,则每个线程下载各下载50Byte,第一条线程下载的开始位置和结束位置为0-50Byte,第二条线程下载的开始位置和结束位置为51-100Byte;
- 开启多条线程下载文件,在下载之前先判断是否存在临时文件,临时文件中保存的是之前文件写入的位置(临时文件由自己生成),如果存在临时文件,则读取临时文件中的数字,将当前的开始位置换为临时文件中的数字;
- 在下载文件的同时,要将文件下载的位置写入到临时文件中,断点续传必须条件(上一步用到,和3对照上看);
- 下载完成后,将临时文件删除掉;
package com.test.download; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; public class MutilDownLoad { // [1]定义下载的路径 private static String path = "http://192.168.33.10:80/test/123456.mp4"; private static final int threadCount = 3; // 假设开三个线程 private static int runningThread; // 代表当前正在运行的线程 public static void main(String[] args) { // [一]获取服务器文件的大小 要计算每个线程下载的开始位置和结束位置 try { // (1) 创建一个url对象 参数就是网址 URL url = new URL(path); // (2)获取HttpURLConnection 链接对象 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); // (3)设置参数 发送get请求 conn.setRequestMethod("GET"); // 默认请求 就是get 要大写 // (4)设置链接网络的超时时间 conn.setConnectTimeout(5000); // (5)获取服务器返回的状态码 int code = conn.getResponseCode(); // 200 代表获取服务器资源全部成功 206请求部分资源 if (code == 200) { // (6)获取服务器文件的大小 int length = conn.getContentLength(); // [6.1]把 线程的数量赋值给正在运行的线程 runningThread = threadCount; System.out.println("length:" + length); // [二] 创建一个大小和服务器一模一样的文件 目的提前把空间申请出来 RandomAccessFile rafAccessFile = new RandomAccessFile("F:\\" + getFilename(path), "rw"); rafAccessFile.setLength(length); // (7)算出每个线程下载的大小 int blockSize = length / threadCount; // [三 计算每个线程下载的开始位置和结束位置 ] for (int i = 0; i < threadCount; i++) { int startIndex = i * blockSize; // 每个线程下载的开始位置 int endIndex = (i + 1) * blockSize - 1; // 特殊情况 就是最后一个线程 if (i == threadCount - 1) { // 说明是最后一个线程 endIndex = length - 1; } System.out.println("线程id:::" + i + "理论下载的位置" + ":" + startIndex + "-----" + endIndex); // 四 开启线程去服务器下载文件 DownLoadThread downLoadThread = new DownLoadThread( startIndex, endIndex, i); downLoadThread.start(); } } } catch (Exception e) { e.printStackTrace(); } } // 定义线程去服务器下载文件 private static class DownLoadThread extends Thread { // 通过构造方法把每个线程下载的开始位置和结束位置传递进来 private int startIndex; private int endIndex; private int threadId; public DownLoadThread(int startIndex, int endIndex, int threadId) { this.startIndex = startIndex; this.endIndex = endIndex; this.threadId = threadId; } @Override public void run() { // 四 实现去服务器下载文件的逻辑 try { // (1) 创建一个url对象 参数就是网址 URL url = new URL(path); // (2)获取HttpURLConnection 链接对象 HttpURLConnection conn = (HttpURLConnection) url .openConnection(); // (3)设置参数 发送POST请求 conn.setRequestMethod("POST"); // 默认请求 就是get 要大写 // (4)设置链接网络的超时时间 conn.setConnectTimeout(5000); // [4.0]如果中间断过 继续上次的位置 继续下载 从文件中读取上次下载的位置 File file = new File("F:\\" + getFilename(path) + threadId + ".txt"); if (file.exists() && file.length() > 0) { FileInputStream fis = new FileInputStream(file); BufferedReader bufr = new BufferedReader( new InputStreamReader(fis)); String lastPositionn = bufr.readLine(); // 读取出来的内容就是上一次下载的位置 int lastPosition = Integer.parseInt(lastPositionn); // [4.0.1]要改变一下 startIndex 位置 startIndex = lastPosition + 1; System.out.println("线程id::" + threadId + "真实下载的位置" + ":" + startIndex + "-----" + endIndex); fis.close(); // 关闭流 } // [4.1]设置一个请求头Range (作用告诉服务器每个线程下载的开始位置和结束位置) conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex); // (5)获取服务器返回的状态码 int code = conn.getResponseCode(); // 200 代表获取服务器资源全部成功 // 206请求部分资源 成功 if (code == 206) { // [6]创建随机读写文件对象 RandomAccessFile raf = new RandomAccessFile("F:\\" + getFilename(path), "rw"); // [6]每个线程要从自己的位置开始写 raf.seek(startIndex); InputStream in = conn.getInputStream(); // [7]把数据写到文件中 int len = -1; byte[] buffer = new byte[1024 * 1024];// 1Mb int total = 0; // 代表当前线程下载的大小 while ((len = in.read(buffer)) != -1) { raf.write(buffer, 0, len); total += len; // [8]实现断点续传 就是把当前线程下载的位置 给存起来 下次再下载的时候 就是按照上次下载的位置继续下载 // 就可以了 int currentThreadPosition = startIndex + total; // 比如就存到一个普通的.txt文本中 // [9]用来存当前线程下载的位置 RandomAccessFile raff = new RandomAccessFile("F:\\" + getFilename(path) + threadId + ".txt", "rwd"); raff.write(String.valueOf(currentThreadPosition) .getBytes()); raff.close(); } raf.close();// 关闭流 释放资源 System.out.println("线程id:" + threadId + "---下载完毕了"); // [10]把.txt文件删除 每个线程具体什么时候下载完毕了 我们不知道 synchronized (DownLoadThread.class) { runningThread--; if (runningThread == 0) { // 说明所有的线程都执行完毕了 就把.txt文件删除 for (int i = 0; i < threadCount; i++) { File delteFile = new File("F:\\" + getFilename(path) + i + ".txt"); delteFile.delete(); } } } } } catch (Exception e) { e.printStackTrace(); } } } // 获取文件的名字 public static String getFilename(String path) { int start = path.lastIndexOf("/") + 1; return path.substring(start); } }