Java基础之多线程断点下载
2016-07-08 20:19
471 查看
基本原理:
首先利用URLConnection获取想要下载的文件的长度,然后由URLConnection获取输入流,根据文件的长度以及下载线程的个数,将文件分成固定大小的块,每一块单独启动一个线程读取、写入。通过输入流读取下载文件的信息,然后将读取的信息用RandomAccessFile随机写入到本地文件中。同时,在每个线程中需要用一个临时文件来保存当前已经下载文件的长度,这样的话如果本次下载没有完成,下一次可以在原来已经下载好的基础上继续下载,就不需要重新下载原来已经下过的部分。代码如下
如果想要多线程下载只需要传入文件下载路径,文件保存路径以及线程的个数,便可以将文件下载至本地。package com.xqq.mutilthreaddownload; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 多线程下载 * @author xqq */ public class MutilThreadDownload extends Thread { private String downloadPath; private String savePath; private int nThread; private final ExecutorService exec; public MutilThreadDownload(String downloadPath, String savePath, int nThread) { this.downloadPath = downloadPath; this.savePath = savePath; this.nThread = nThread; this.exec = Executors.newFixedThreadPool(nThread); } @Override public void run() { try { // 连接获取文件信息 URL url = new URL(downloadPath); 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); //每个线程所需下载文件的大小 int blockSize = length / nThread; for (int i = 1; i <= nThread; i++) { int startIndex = (i - 1) * blockSize; int endIndex = i * blockSize - 1; if (i == nThread) { endIndex = length; } //开始下载文件 exec.execute(new Download(startIndex, endIndex, downloadPath, i, savePath)); } } else { System.out.println("文件错误......"); } } catch (MalformedURLException e) { System.out.println("MalformedURLException"); } catch (IOException e) { System.out.println("IOException"); } //当所有线程均下载完成之后,关闭线程池 exec.shutdown(); } }
根据文件开始和结束位置从输入流中读取文件并利用RandomAccessFile保存至相应位置。
package com.xqq.mutilthreaddownload; 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 Download implements Runnable{ private int startIndex; private int endIndex; private String path; private static int runningThread = Test.nThread; private int threadID; private String savePath; public Download(int startIndex, int endIndex, String path, int threadID, String savePath) { this.startIndex = startIndex; this.endIndex = endIndex; this.path = path; this.threadID = threadID; this.savePath = savePath; } public void run() { try { File tempFile = new File(savePath + threadID + ".txt"); if(tempFile.exists() && tempFile.length() > 0){ FileInputStream fis = new FileInputStream(tempFile); byte [] temp = new byte[1024]; int length = fis.read(temp); String downloadLen = new String(temp, 0, length); startIndex = Integer.parseInt(downloadLen); fis.close(); } System.out.println(Thread.currentThread() + " 下载范围: " + startIndex + " - >" + endIndex); URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex); conn.setConnectTimeout(5000); conn.setRequestMethod("GET"); int code = conn.getResponseCode(); //获取部分数据,成功返回206 if(code == 206){ RandomAccessFile file = new RandomAccessFile(savePath + "setup.exe", "rwd"); file.seek(startIndex);//定位到文件的位置 InputStream in = conn.getInputStream(); byte [] buffer = new byte[1024 * 2]; int len = 0; int total = 0; while((len = in.read(buffer)) != -1){ RandomAccessFile totalFile = new RandomAccessFile(savePath + threadID + ".txt", "rwd"); file.write(buffer, 0, len); total += len; totalFile.write((total + startIndex + "").getBytes()); totalFile.close(); } file.close(); }else{ System.out.println(Thread.currentThread() + " 下载失败"); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ System.out.println(Thread.currentThread() + " 下载完成....."); runningThread --; if(runningThread == 0){ for(int i = 1; i <= Test.nThread; i++){ File file = new File(savePath + i + ".txt"); file.delete(); } } } } }
测试文件
package com.xqq.mutilthreaddownload; public class Test { private final static String downloadPath = "http://localhost:8080/123.exe"; private static final String savePath = "c://"; public static final int nThread = 5; public static void main(String[] args) { MutilThreadDownload thread = new MutilThreadDownload(downloadPath,savePath, nThread); thread.start(); } } 测试结果: 文件的总长度为: 13087904 Thread[pool-1-thread-2,5,main] 下载范围: 3283180 - >5235159 Thread[pool-1-thread-3,5,main] 下载范围: 5900760 - >7852739 Thread[pool-1-thread-4,5,main] 下载范围: 8518340 - >10470319 Thread[pool-1-thread-5,5,main] 下载范围: 11133872 - >13087904 Thread[pool-1-thread-1,5,main] 下载范围: 665600 - >2617579 Thread[pool-1-thread-4,5,main] 下载完成..... Thread[pool-1-thread-2,5,main] 下载完成..... Thread[pool-1-thread-3,5,main] 下载完成..... Thread[pool-1-thread-5,5,main] 下载完成..... Thread[pool-1-thread-1,5,main] 下载完成.....
总结:
我今天突然发现,使用了线程池,如果你不调用shutdown()或者shutdownNow()显示的关闭线程池,那么main函数就不会停止,会一直等待线程加入线程池中运行。知道了这个之后,我就顿悟了为什么之前我在Android开发的时候使用了线程池却一直打印线程太多的问题。相关文章推荐
- Java反射学习一
- Java反射学习二
- Java反射学习三
- Java反射学习四
- Spring-3:bean的属性配置细节
- java中volatile关键字的含义
- 记录:git删除误提交的Eclipse项目文件与配置文件
- 让eclipse启动时拥有jre
- ubuntu15.10 给解压版的eclipse安装桌面快捷方式
- 利用javaMail发送邮件
- java编程思想读书笔记
- 搭建Spring mvc环境
- 关于eclipse中配置svn插件,亲测有效。
- target,currentTarget和this三者的区别
- Jpanel或Jframe添加键盘监听无效
- Struts2的Action向JSP传输List集合并且将数据显示在页面上
- Java动态代理
- Java 集合 散列表hash table
- Java8系列之重新认识HashMap
- 个人学习-java-jdbc学习