Java多线程断点续传代码详解

来源:互联网 发布:连衣裙 知乎 编辑:程序博客网 时间:2024/05/29 14:51

多线程断点续传下载的几处关键步骤:

1.创建临时文件空间并计算每个线程需要下载的部分size

2.每个线程需要下载的开始和结束startIndex和endIndexe,使用获取的流的长度除以size就得到了每个线程需要下载的部分大小,但是如果除不尽的都留给最后一个线程来下载,

3.保证断点续传的机制,使用的是临时文件来存放每个线程下载的进度,每次开始下载前先判断一下该临时文件是否存在,以及记录的对应的线程下载记录,根据记录开始本次下载,判断每个线程是否下载完毕,若果都下载完毕则通过循环删除临时文件,为避免并发操作应该对删除临时文件加锁。

4.具体下载过程,再次获取连接,根据206的状态码获取读取流,然后就是Java中的io读写的过程了,需要用到的RandomAccessFile来实现临时文件的创建和写入,简单说是因为每个线程请求的部分不同,因而写入的时候也不是从零开始写的,而RandomAccessFile的seek方法,支持写入时的定位,也就是 在指定位置写入文件,然后几个部分拼凑起来就是完成的要下载的文件了。

5.此外还有需要注意的细节,比如请求网络的代码要放在子线程中来实现,即具体下载过程部分的代码。

以下是具体实现代码,代码中包含详细注解:

package com.nocol.mutildownload;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;/** * @author lxp * * @TODO *  */public class MutilDownLoad {//获取文件下载路径public static String path = "http://192.168.56.1:8080/haozip.exe";// 定义线程个数public static int ThreadCount = 3;// ===定义正在运行的线程===public static int RunningThread;public static void main(String[] args) throws Exception {// 连接服务器,获取下载的文件长度// 创建URL对象URL url = new URL(path);// 获取连接对象HttpURLConnection conn = (HttpURLConnection) url.openConnection();// 设置请求数据的方式conn.setRequestMethod("GET");// 设置请求超时时间conn.setReadTimeout(5000);// 获取服务器状态码int code = conn.getResponseCode();if (code == 200) {// 获取文件实际长度int lenth = conn.getContentLength();// ===将线程个数赋值给正在运行的线程====RunningThread = ThreadCount;// 创建要下载文件的文件空间RandomAccessFile raf = new RandomAccessFile(getName(path), "rw");// 指定该空间的长度(和要下载的文件的长度相同)raf.setLength(lenth);raf.close();// 设置每个线程要下载的文件长度int blockSize = lenth / ThreadCount;for (int ThreadId = 1; ThreadId <= ThreadCount; ThreadId++) {// 设置没每个线程下载的开始位置int startIndex = (ThreadId - 1) * blockSize;int endIndex = startIndex + (blockSize - 1);if (ThreadId == ThreadCount) {endIndex = lenth;}System.out.println(ThreadId + "理论下载的大小:" + startIndex + "-----" + endIndex);// 开启线程下载DownLoadThread downLoadThread=new DownLoadThread(startIndex, endIndex, ThreadId);downLoadThread.start();}}}// 创建线程下载文件public static class DownLoadThread extends Thread {//静态内部类private int startIndex;private int endIndex;private int ThreadId;public DownLoadThread(int startIndex, int endIndex, int ThreadId) {this.endIndex = endIndex;this.startIndex = startIndex;this.ThreadId = ThreadId;}@Overridepublic void run() {try {// 创建URL对象URL url = new URL(path);// 获取连接对象HttpURLConnection conn = (HttpURLConnection) url.openConnection();// 设置请求数据的方式conn.setRequestMethod("GET");// 设置请求超时时间conn.setReadTimeout(5000);// ==将记录线程位置 的文件封装成File对象File file = new File(ThreadId + ".txt");// ==下载前判断是否存在保存线程下载位置的文件来判断是否要进行断点续传if (file.exists()) {FileInputStream fis = new FileInputStream(file);BufferedReader br = new BufferedReader(new InputStreamReader(fis));// 获取线程下载的最后的位置String Lastpostion = br.readLine();// 装换为int类型int LastPositionn = Integer.parseInt(Lastpostion);// 将该位置赋值给起始位置startIndex = LastPositionn;// 释放资源fis.close();System.out.println(ThreadId + "实际下载的大小:" + startIndex + "-----" + endIndex);}// 很重要:请求服务器下载部分的文件的指定的位置(请求头信息)conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);// 获取服务器状态码int code = conn.getResponseCode();if (code == 206) {// 状态码=206表示请求部分资源成功// 获取输入流对象InputStream in = conn.getInputStream();// 创建随机访问文件对象RandomAccessFile raf = new RandomAccessFile(MutilDownLoad.getName(path), "rw");// 指定线程下载文件的开始位置raf.seek(startIndex);// 将读取的文件写入创建的文件空间int len = 0;byte[] bys = new byte[1024 * 1024];// ===定义当前线程下载的大小int total = 0;while ((len = in.read(bys)) != -1) {raf.write(bys, 0, len);// ==total即为每个线程从各自其实位置开始下载的文件大小total += len;// ==为实现断点续传,获取当前线程下载的位置int CurrentThreadPosition = startIndex + total;// ==将当前线程下载的位置记录下来(.txt文件),定义随机访问而文件对象// =="rwd" 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。RandomAccessFile raff = new RandomAccessFile(ThreadId + ".txt", "rwd");raff.write(String.valueOf(CurrentThreadPosition).getBytes());raff.close();}// 释放资源in.close();raf.close();System.out.println("线程" + ThreadId + "下载完成!");// ===在每个线程下载完毕后将每个线程的".txt"文件删除,为避免并发操作,为删文件操作加锁====synchronized (DownLoadThread.class) {RunningThread--;// 线程下载完成后将不在运行,运行中的线程个数相应减少if (RunningThread == 0) {// 当RunningThread个数为0时说明文件全部下载完成for (int i = 1; i <= ThreadCount; i++) {File deleteFile = new File(i + ".txt");deleteFile.delete();}}}}} catch (Exception e) {e.printStackTrace();}}}//获取源文件的文件名public static String getName(String path){//http://192.168.56.1:8080/haozip.exe//获取"/"最后一次出现的索引int index=path.lastIndexOf("/");String name=path.substring(index+1);return name;}}


1 0