zoukankan      html  css  js  c++  java
  • 带断点续传的多线程下载

    • 多线程下载

    原理:服务器CPU分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源,所以使用多线程下载的话,速度会更快

    JavaSE实现带断点续传的多线程下载步骤:

      1、发送http请求至下载地址,获取要下载的资源文件的大小

      2、根据资源文件的大小,创建一个长度一样的临时文件,用来抢占磁盘空间

      3、计算每个线程要下载的数据大小和开始位置、结束位置,余数都由最后一个线程完成下载,所以最后一个线程的结束位置要写死

      4、再次发送请求,请求要下载的数据区间的数据(判断是否有记录进度的临时文件,有的话就继续上次位置接着下载,没有就从原本开始位置下载)

      5、将下载请求到的数据,存储到临时文件中(新建一个记录下载进度的临时文件)

      6、等所有线程都下载完毕了,就要将之前的记录进度的临时文件删除掉

      1 package com.ahu.multithreaddownload;
      2 
      3 import java.io.BufferedReader;
      4 import java.io.File;
      5 import java.io.FileInputStream;
      6 import java.io.InputStream;
      7 import java.io.InputStreamReader;
      8 import java.io.RandomAccessFile;
      9 import java.net.HttpURLConnection;
     10 import java.net.URL;
     11 
     12 /**
     13  * 带断点续传的多线程下载
     14  * 
     15  * @author ahu_lichang
     16  * 
     17  */
     18 public class MultiThreadDownload {
     19     // 线程数
     20     static int ThreadCount = 4;
     21     static int finishedThread = 0;
     22 
     23     static String path = "http://172.23.13.179:8080/report.ppt";
     24 
     25     public static void main(String[] args) {
     26         try {
     27             // 第一次请求服务器,是为了获取资源文件的大小,从而创建一个相同大小的临时文件,而不是下载资源!
     28             URL url = new URL(path);
     29             HttpURLConnection connection = (HttpURLConnection) url
     30                     .openConnection();
     31             connection.setConnectTimeout(5000);
     32             connection.setReadTimeout(5000);
     33             connection.setRequestMethod("GET");
     34             //connection.connect();
     35             if (connection.getResponseCode() == 200) {
     36                 // 获取要下载文件的大小
     37                 int length = connection.getContentLength();
     38                 // 生成临时文件
     39                 RandomAccessFile raf = new RandomAccessFile(getFileName(path),
     40                         "rwd");
     41                 raf.setLength(length);
     42                 raf.close();
     43                 // 计算每个线程下载的资源大小(有可能存在余数,余数都放在最后一个线程里)
     44                 int size = length / ThreadCount;
     45                 // 计算每个线程下载的开始位置和结束位置
     46                 for (int i = 0; i < ThreadCount; i++) {
     47                     int startIndex = i * size;
     48                     int endIndex = (i + 1) * size - 1;
     49                     // 如果是最后一个线程,必须将结束位置写死
     50                     if (i == ThreadCount - 1) {
     51                         endIndex = length - 1;
     52                     }
     53                     // 开启线程下载资源
     54                     new DownloadThread(startIndex, endIndex, i).start();
     55                 }
     56             }
     57         } catch (Exception e) {
     58             e.printStackTrace();
     59         }
     60 
     61     }
     62 
     63     /**
     64      * 获取服务器中资源的名称
     65      * 
     66      * @param path
     67      * @return
     68      */
     69     public static String getFileName(String path) {
     70         int index = path.lastIndexOf("/");
     71         return path.substring(index + 1);
     72     }
     73 
     74 }
     75 /**
     76  * 下载线程
     77  * @author ahu_lichang
     78  *
     79  */
     80 class DownloadThread extends Thread {
     81     int startIndex;
     82     int endIndex;
     83     int threadId;
     84 
     85     public DownloadThread(int startIndex, int endIndex, int threadId) {
     86         super();
     87         this.startIndex = startIndex;
     88         this.endIndex = endIndex;
     89         this.threadId = threadId;
     90     }
     91 
     92     public void run() {
     93         try {
     94             File progressFile = new File(threadId + ".txt");
     95             //如果存在进度临时文件,就接着上次的后面进行下载
     96             if (progressFile.exists()) {
     97                 FileInputStream fis = new FileInputStream(progressFile);
     98                 BufferedReader br = new BufferedReader(new InputStreamReader(
     99                         fis));
    100                 // 得到上一次下载的总进度,然后与原本的开始位置相加,得到新的开始位置
    101                 startIndex += Integer.parseInt(br.readLine());
    102                 fis.close();
    103             }
    104             System.out.println("线程" + threadId + "下载区间" + startIndex + "---"
    105                     + endIndex);
    106             // 再次请求服务器,下载资源文件
    107             URL url = new URL(MultiThreadDownload.path);
    108             HttpURLConnection connection = (HttpURLConnection) url
    109                     .openConnection();
    110             connection.setConnectTimeout(5000);
    111             connection.setReadTimeout(5000);
    112             connection.setRequestMethod("GET");
    113             // 设置本次所请求的数据区间
    114             connection.setRequestProperty("Range", "bytes=" + startIndex + "-"
    115                     + endIndex);
    116             //connection.connect();
    117             // 请求部分数据的响应码是206
    118             if (connection.getResponseCode() == 206) {
    119                 // 拿到1/3源文件的数据
    120                 InputStream is = connection.getInputStream();
    121                 byte[] b = new byte[1024];
    122                 int len = 0;
    123                 int total = 0;// 记录下载到临时文件中数据的大小
    124                 // 把拿到的数据写入到临时文件中
    125                 RandomAccessFile raf = new RandomAccessFile(
    126                         MultiThreadDownload
    127                                 .getFileName(MultiThreadDownload.path),
    128                         "rwd");
    129                 // 把文件的写入位置移动至startIndex。这样才不会覆盖写入
    130                 raf.seek(startIndex);
    131                 while ((len = is.read(b)) != -1) {
    132                     raf.write(b, 0, len);
    133                     total += len;
    134                     // 生成用来记录下载进度的临时文件
    135                     RandomAccessFile progressRaf = new RandomAccessFile(
    136                             progressFile, "rwd");
    137                     progressRaf.write((total + "").getBytes());
    138                     progressRaf.close();
    139                 }
    140                 System.out.println("线程" + threadId + "下载完毕!!!");
    141                 raf.close();
    142 
    143                 // 全部下载完成后,将临时存放进度的文件删除
    144                 MultiThreadDownload.finishedThread++;
    145                 // 注意线程安全问题
    146                 synchronized (MultiThreadDownload.path) {
    147                     if (MultiThreadDownload.finishedThread == MultiThreadDownload.ThreadCount) {
    148                         for (int i = 0; i < MultiThreadDownload.ThreadCount; i++) {
    149                             File f = new File(i + ".txt");
    150                             f.delete();
    151                         }
    152                         MultiThreadDownload.finishedThread = 0;
    153                     }
    154                 }
    155             }
    156         } catch (Exception e) {
    157             e.printStackTrace();
    158         }
    159     }
    160 }
    View Code
    • Android上实现带断点续传的多线程下载

    布局文件:

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     3     xmlns:tools="http://schemas.android.com/tools"
     4     android:id="@+id/activity_main"
     5     android:layout_width="match_parent"
     6     android:layout_height="match_parent"
     7     android:paddingBottom="@dimen/activity_vertical_margin"
     8     android:paddingLeft="@dimen/activity_horizontal_margin"
     9     android:paddingRight="@dimen/activity_horizontal_margin"
    10     android:paddingTop="@dimen/activity_vertical_margin"
    11     tools:context="com.ahu.lichang.multithreaddownload.MainActivity"
    12     android:orientation="vertical">
    13     <Button
    14         android:text="多线程下载"
    15         android:onClick="download"
    16         android:layout_width="wrap_content"
    17         android:layout_height="wrap_content" />
    18 
    19     <ProgressBar
    20         android:id="@+id/pb"
    21         style="@android:style/Widget.ProgressBar.Horizontal"
    22         android:layout_width="match_parent"
    23         android:layout_height="wrap_content" />
    24     <TextView
    25         android:id="@+id/tv"
    26         android:layout_width="wrap_content"
    27         android:layout_height="wrap_content"
    28         android:text="下载进度:0%" />
    29 
    30 </LinearLayout>
    View Code

       MainActivity:

      1 package com.ahu.lichang.multithreaddownload;
      2 
      3 import android.app.Activity;
      4 import android.os.Bundle;
      5 import android.os.Environment;
      6 import android.os.Handler;
      7 import android.view.View;
      8 import android.widget.ProgressBar;
      9 import android.widget.TextView;
     10 
     11 import java.io.BufferedReader;
     12 import java.io.File;
     13 import java.io.FileInputStream;
     14 import java.io.InputStream;
     15 import java.io.InputStreamReader;
     16 import java.io.RandomAccessFile;
     17 import java.net.HttpURLConnection;
     18 import java.net.URL;
     19 
     20 public class MainActivity extends Activity {
     21     static int ThreadCount = 3;
     22     static int finishedThread = 0;
     23 
     24     int currentProgress;
     25     String fileName = "QQPlayer.exe";
     26     //确定下载地址
     27     String path = "http://172.23.13.179:8080/" + fileName;
     28     private ProgressBar pb;
     29     TextView tv;
     30 
     31     Handler handler = new Handler(){
     32         public void handleMessage(android.os.Message msg) {
     33             //把变量改成long,在long下运算
     34             tv.setText((long)pb.getProgress() * 100 / pb.getMax() + "%");
     35         }
     36     };
     37     @Override
     38     protected void onCreate(Bundle savedInstanceState) {
     39         super.onCreate(savedInstanceState);
     40         setContentView(R.layout.activity_main);
     41         pb = (ProgressBar) findViewById(R.id.pb);
     42         tv = (TextView) findViewById(R.id.tv);
     43     }
     44     public void download(View view){
     45         Thread t = new Thread(){
     46             @Override
     47             public void run() {
     48                 //发送get请求,请求这个地址的资源
     49                 try {
     50                     URL url = new URL(path);
     51                     HttpURLConnection conn = (HttpURLConnection) url.openConnection();
     52                     conn.setRequestMethod("GET");
     53                     conn.setConnectTimeout(5000);
     54                     conn.setReadTimeout(5000);
     55 
     56                     if(conn.getResponseCode() == 200){
     57                         //拿到所请求资源文件的长度
     58                         int length = conn.getContentLength();
     59 
     60                         //设置进度条的最大值就是原文件的总长度
     61                         pb.setMax(length);
     62 
     63                         File file = new File(Environment.getExternalStorageDirectory(), fileName);
     64                         //生成临时文件
     65                         RandomAccessFile raf = new RandomAccessFile(file, "rwd");
     66                         //设置临时文件的大小
     67                         raf.setLength(length);
     68                         raf.close();
     69                         //计算出每个线程应该下载多少字节
     70                         int size = length / ThreadCount;
     71                         for (int i = 0; i < ThreadCount; i++) {
     72                             //计算线程下载的开始位置和结束位置
     73                             int startIndex = i * size;
     74                             int endIndex = (i + 1) * size - 1;
     75                             //如果是最后一个线程,那么结束位置写死
     76                             if(i == ThreadCount - 1){
     77                                 endIndex = length - 1;
     78                             }
     79                             new DownLoadThread(startIndex, endIndex, i).start();
     80                         }
     81                     }
     82                 } catch (Exception e) {
     83                     e.printStackTrace();
     84                 }
     85             }
     86         };
     87         t.start();
     88     }
     89     class DownLoadThread extends Thread{
     90         int startIndex;
     91         int endIndex;
     92         int threadId;
     93 
     94         public DownLoadThread(int startIndex, int endIndex, int threadId) {
     95             super();
     96             this.startIndex = startIndex;
     97             this.endIndex = endIndex;
     98             this.threadId = threadId;
     99         }
    100 
    101         @Override
    102         public void run() {
    103             //再次发送http请求,下载原文件
    104             try {
    105                 File progressFile = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");
    106                 //判断进度临时文件是否存在
    107                 if(progressFile.exists()){
    108                     FileInputStream fis = new FileInputStream(progressFile);
    109                     BufferedReader br = new BufferedReader(new InputStreamReader(fis));
    110                     //从进度临时文件中读取出上一次下载的总进度,然后与原本的开始位置相加,得到新的开始位置
    111                     int lastProgress = Integer.parseInt(br.readLine());
    112                     startIndex += lastProgress;
    113 
    114                     //把上次下载的进度显示至进度条
    115                     currentProgress += lastProgress;
    116                     pb.setProgress(currentProgress);
    117                     //发送消息,让主线程刷新文本进度
    118                     handler.sendEmptyMessage(1);
    119 
    120                     fis.close();
    121                 }
    122                 System.out.println("线程" + threadId + "的下载区间是:" + startIndex + "---" + endIndex);
    123                 HttpURLConnection conn;
    124                 URL url = new URL(path);
    125                 conn = (HttpURLConnection) url.openConnection();
    126                 conn.setRequestMethod("GET");
    127                 conn.setConnectTimeout(5000);
    128                 conn.setReadTimeout(5000);
    129                 //设置本次http请求所请求的数据的区间
    130                 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
    131 
    132                 //请求部分数据,相应码是206
    133                 if(conn.getResponseCode() == 206){
    134                     //流里此时只有1/3原文件的数据
    135                     InputStream is = conn.getInputStream();
    136                     byte[] b = new byte[1024];
    137                     int len = 0;
    138                     int total = 0;
    139                     //拿到临时文件的输出流
    140                     File file = new File(Environment.getExternalStorageDirectory(), fileName);
    141                     RandomAccessFile raf = new RandomAccessFile(file, "rwd");
    142                     //把文件的写入位置移动至startIndex
    143                     raf.seek(startIndex);
    144                     while((len = is.read(b)) != -1){
    145                         //每次读取流里数据之后,同步把数据写入临时文件
    146                         raf.write(b, 0, len);
    147                         total += len;
    148                         System.out.println("线程" + threadId + "下载了" + total);
    149 
    150                         //每次读取流里数据之后,把本次读取的数据的长度显示至进度条
    151                         currentProgress += len;
    152                         pb.setProgress(currentProgress);
    153                         //发送消息,让主线程刷新文本进度
    154                         handler.sendEmptyMessage(1);
    155 
    156                         //生成一个专门用来记录下载进度的临时文件
    157                         RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");
    158                         //每次读取流里数据之后,同步把当前线程下载的总进度写入进度临时文件中
    159                         progressRaf.write((total + "").getBytes());
    160                         progressRaf.close();
    161                     }
    162                     System.out.println("线程" + threadId + "下载完毕");
    163                     raf.close();
    164 
    165                     finishedThread++;
    166                     synchronized (path) {
    167                         if(finishedThread == ThreadCount){
    168                             for (int i = 0; i < ThreadCount; i++) {
    169                                 File f = new File(Environment.getExternalStorageDirectory(), i + ".txt");
    170                                 f.delete();
    171                             }
    172                             finishedThread = 0;
    173                         }
    174                     }
    175                 }
    176             } catch (Exception e) {
    177                 e.printStackTrace();
    178             }
    179         }
    180     }
    181 }
    View Code
    1     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    2     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    3     <uses-permission android:name="android.permission.INTERNET"/>

     

  • 相关阅读:
    关于宿命论的一点杂想
    关于平权意识
    《天语物道:李政道评传》
    这段时间的杂想
    Spring-Cloud简易全家桶实践
    spring-boot-starter实践
    docker 本地环境安装流程和基本指令
    SpringBoot启动关键点解析 及启动日志追溯
    Bean加载机制解读
    Spring Boot 启动机制源码阅读(粗略)
  • 原文地址:https://www.cnblogs.com/ahu-lichang/p/6594009.html
Copyright © 2011-2022 走看看