博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java代码实现多线程下载和断点续传
阅读量:4182 次
发布时间:2019-05-26

本文共 6372 字,大约阅读时间需要 21 分钟。

多线程下载原理

  • 客户端要下载一个文件, 首先请求服务器,服务器将这个文件传送给客户端,客户端保存到本地, 完成了一个下载的过程.
  • 多线程下载的思想是客户端开启多个线程同时下载,每个线程只负责下载文件的一部分, 当所有线程下载完成的时候,文件下载完毕. 
    • 并不是线程越多下载越快, 与网络环境有很大的关系
    • 在同等的网络环境下,多线程下载速度要高于单线程.
    • 多线程下载占用资源比单线程多,相当于用资源换取速度

java代码实现多线程下载

代码的思路:

  1. 首先要获取要下载文件的大小
  2. 在磁盘上使用RandomAccessFile 这个类在磁盘上创建一个大小一样的文件,将来将数据写入这个文件.
  3. 为每个线程分配下载任务. 内容包括线程现在文件的开始位置和结束位置.这里面有一点数学知识,代码中有备注.
  4. 启动下载线程
  5. 判断有没有保存上次下载的临时文件.
  6. 在启动线程下载的时候保存下载的位置信息
  7. 下载完毕后删除当前线程产生的临时文件
package com.yb.muchthreaddown;import java.io.File;import java.io.InputStream;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;/** * 多线程下载 和 断点续传 * @author 杨斌. * */public class MuchThreadDown {
// private String path = "http://mpge.5nd.com/2016/2016-11-15/74847/1.mp3"; //下载路径 private String path = "http://117.169.69.238/mp3.9ku.com/m4a/186947.m4a"; private String targetFilePath="/"; //下载文件存放目录 private int threadCount = 3; //线程数量 /** * 构造方法 * @param path 要下载文件的网络路径 * @param targetFilePath 保存下载文件的目录 * @param threadCount 开启的线程数量,默认为 3 */ public MuchThreadDown(String path, String targetFilePath, int threadCount) { this.path = path; this.targetFilePath = targetFilePath; this.threadCount = threadCount; } /** * 下载文件 */ public void download() throws Exception{ //连接资源 URL url = new URL(path); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(10000); int code = connection.getResponseCode(); if(code == 200){ //获取资源大小 int connectionLength = connection.getContentLength(); System.out.println(connectionLength); //在本地创建一个与资源同样大小的文件来占位 RandomAccessFile randomAccessFile = new RandomAccessFile(new File(targetFilePath,getFileName(url)), "rw"); randomAccessFile.setLength(connectionLength); /* * 将下载任务分配给每个线程 */ int blockSize = connectionLength/threadCount;//计算每个线程理论上下载的数量. for(int threadId = 0; threadId < threadCount; threadId++){
//为每个线程分配任务 int startIndex = threadId * blockSize; //线程开始下载的位置 int endIndex = (threadId+1) * blockSize -1; //线程结束下载的位置 if(threadId == (threadCount - 1)){ //如果是最后一个线程,将剩下的文件全部交给这个线程完成 endIndex = connectionLength - 1; } new DownloadThread(threadId, startIndex, endIndex).start();//开启线程下载 }// randomAccessFile.close(); } } //下载的线程 private class DownloadThread extends Thread{
private int threadId; private int startIndex; private int endIndex; public DownloadThread(int threadId, int startIndex, int endIndex) { this.threadId = threadId; this.startIndex = startIndex; this.endIndex = endIndex; } @Override public void run() { System.out.println("线程"+ threadId + "开始下载"); try { //分段请求网络连接,分段将文件保存到本地. URL url = new URL(path); //加载下载位置的文件 File downThreadFile = new File(targetFilePath,"downThread_" + threadId+".dt"); RandomAccessFile downThreadStream = null; if(downThreadFile.exists()){
//如果文件存在 downThreadStream = new RandomAccessFile(downThreadFile,"rwd"); String startIndex_str = downThreadStream.readLine(); if(null==startIndex_str||"".equals(startIndex_str)){ //网友 imonHu 2017/5/22 this.startIndex=startIndex; }else{ this.startIndex = Integer.parseInt(startIndex_str)-1;//设置下载起点 } }else{ downThreadStream = new RandomAccessFile(downThreadFile,"rwd"); } HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(10000); //设置分段下载的头信息。 Range:做分段数据请求用的。格式: Range bytes=0-1024 或者 bytes:0-1024 connection.setRequestProperty("Range", "bytes="+ startIndex + "-" + endIndex); System.out.println("线程_"+threadId + "的下载起点是 " + startIndex + " 下载终点是: " + endIndex); if(connection.getResponseCode() == 206){
//200:请求全部资源成功, 206代表部分资源请求成功 InputStream inputStream = connection.getInputStream();//获取流 RandomAccessFile randomAccessFile = new RandomAccessFile( new File(targetFilePath,getFileName(url)), "rw");//获取前面已创建的文件. randomAccessFile.seek(startIndex);//文件写入的开始位置. /* * 将网络流中的文件写入本地 */ byte[] buffer = new byte[1024]; int length = -1; int total = 0;//记录本次下载文件的大小 while((length = inputStream.read(buffer)) > 0){ randomAccessFile.write(buffer, 0, length); total += length; /* * 将当前现在到的位置保存到文件中 */ downThreadStream.seek(0); downThreadStream.write((startIndex + total + "").getBytes("UTF-8")); } downThreadStream.close(); inputStream.close(); randomAccessFile.close(); cleanTemp(downThreadFile);//删除临时文件 System.out.println("线程"+ threadId + "下载完毕"); }else{ System.out.println("响应码是" +connection.getResponseCode() + ". 服务器不支持多线程下载"); } } catch (Exception e) { e.printStackTrace(); } } } //删除线程产生的临时文件 private synchronized void cleanTemp(File file){ file.delete(); } //获取下载文件的名称 private String getFileName(URL url){ String filename = url.getFile(); return filename.substring(filename.lastIndexOf("/")+1); } public static void main(String[] args) { try { new MuchThreadDown(null, null, 3).download(); } catch (Exception e) { e.printStackTrace(); } }}

转载地址:http://vhzoi.baihongyu.com/

你可能感兴趣的文章
预编译防止sql注入
查看>>
覆盖equals方法时总是要覆盖hashCode
查看>>
clone详解
查看>>
【Java并发编程实战】——AbstractQueuedSynchronizer源码分析(一)
查看>>
【Java并发编程实战】——并发编程基础
查看>>
【Java并发编程实战】——Java内存模型与线程
查看>>
Java复制文件的4种方式
查看>>
mysql的JDBC连接工具类
查看>>
利用多线程(用到原子类AtomicInteger)往数据库批量插入大量数据
查看>>
多个线程操作数组
查看>>
定长线程池的应用
查看>>
ArrayBlockingQueue的简单使用
查看>>
Git 常用命令总结(一)
查看>>
Git 常用命令总结(二)
查看>>
JAVA 并发——synchronized的分析
查看>>
Echarts——使用 dataset 管理数据
查看>>
DES 加解密工具类
查看>>
SpringBoot多模块项目实践(Multi-Module)
查看>>
第一篇: 服务的注册与发现Eureka(Greenwich版)
查看>>
第二篇: 服务消费者(rest+ribbon)(Greenwich版本)
查看>>