zoukankan      html  css  js  c++  java
  • Android(java)学习笔记158:多线程断点下载的原理(JavaSE实现)

    1. 为什么需要多线程下载?

        服务器的资源有限,同时的平均地分配给每个客户端。开启的线程越多抢占的服务的资源就越多,下载的速度就越块。

    2. 下载速度的限制条件?

    (1)你的电脑手机宽带的带宽。(网络运营商给用户的限制

    (2)服务器上传的带宽限制。 (服务器端资源获取速度的限制)----迅雷, p2p快播等下载,可以同时间使用多台服务器帮助用户下载资源,速度自然会加快。

    注意:并不是开的线程越多下载速度越快,可能会消耗大量时间在线程调度上

    Android下推荐开启:3 ~ 5线程。

    3. 如何进行多线程的下载:

    (1)在客户端本地创建一个空文件(申请一块内存),大小要和服务器上要下载的资源一样.

    (2)开启3个线程,都去下载服务器的数据.

    (3)当三个线程都工作完毕后,多线程的下载就结束了.

     这里特别注意最后一个线程需要修正,主要是因为不可能实现完全等分,具体如下:

     

    4.JavaSE代码实现多线程下载

     (1)我们可以先编写java项目工程,调试实现多线程下载逻辑类MutilDownloader.java:

     

     (2)打开Apache服务器,在相应的目录下存放测试下载文件,如下:

    (3)MutilDownloader.java,如下:

      1 package com.himi.mutildownload;
      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  */
     16 public class MutilDownloader {
     17     /**
     18      * Apache服务器上资源下载的路径
     19      */
     20     private static final String path = "http://49.123.76.170/movies/test.avi";
     21     /**
     22      * 多少个线程去下载服务器的资源
     23      */
     24     private static int threadCount = 4;
     25 
     26     /**
     27      * 正在运行的线程的数量
     28      */
     29     private static int runningThreadCount;
     30 
     31     public static void main(String[] args) throws Exception {
     32         URL url = new URL(path);
     33         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
     34         conn.setRequestMethod("GET");
     35         int code = conn.getResponseCode();
     36         if (code == 200) {
     37             int length = conn.getContentLength();
     38             System.out.println("服务器文件的大小为:" + length);
     39 
     40             // 1. 创建一个空白文件文件的大小和服务器资源一样
     41             RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
     42             raf.setLength(length);
     43             raf.close();
     44 
     45             // 每个线程下载的平均区块大小
     46             int blocksize = length / threadCount;
     47             System.out.println("每一份:" + blocksize);
     48 
     49             runningThreadCount = threadCount;
     50             // 2. 开启3个线程,都去下载服务器的对应数据
     51             for (int threadId = 0; threadId < threadCount; threadId++) {
     52                 int startIndex = threadId * blocksize;
     53                 int endIndex = (threadId + 1) * blocksize - 1;
     54 
     55                 // 最后一个线程的修正,最后一个线程endIndex设置为文件末尾
     56                 if (threadId == (threadCount - 1)) {
     57                     endIndex = length - 1;// 文件byte是从0开始计数的
     58                 }
     59 
     60                 new DownloadThread(startIndex, endIndex, threadId).start();
     61             }
     62         }
     63 
     64         // 3. 当三个线程都工作完毕后,多线程的下载就结束了.
     65 
     66     }
     67 
     68     public static class DownloadThread extends Thread {
     69         /**
     70          * 线程id
     71          */
     72         int threadId;
     73 
     74         /**
     75          * 当前线程下载的开始位置
     76          */
     77         int startIndex;
     78         /**
     79          * 当前线程下载的结束位置
     80          */
     81         int endIndex;
     82 
     83         /**
     84          * 当前线程下载到文件的位置
     85          */
     86         int filePosition;
     87 
     88         /**
     89          * 
     90          * @param startIndex
     91          *            开始位置
     92          * @param endIndex
     93          *            结束位置
     94          * @param threadId
     95          *            线程id
     96          */
     97         public DownloadThread(int startIndex, int endIndex, int threadId) {
     98             this.startIndex = startIndex;
     99             this.endIndex = endIndex;
    100             this.threadId = threadId;
    101             filePosition = startIndex;
    102         }
    103 
    104         @Override
    105         public void run() {
    106             try {
    107                 // 用一个文本记录当前线程下载的进程
    108                 File file = new File(threadId + getFileName(path) + ".txt");
    109 
    110                 if (file.exists() && file.length() > 0) {
    111                     FileInputStream fis = new FileInputStream(file);
    112                     BufferedReader br = new BufferedReader(new InputStreamReader(fis));
    113                     filePosition = Integer.parseInt(br.readLine());// 上一次下载到文件的哪个位子。
    114                     startIndex = filePosition;
    115                     fis.close();
    116                 }
    117 
    118                 System.out.println("线程:" + threadId + "实际上下载的位置:" + startIndex + "~~~" + endIndex);
    119 
    120                 URL url = new URL(path);
    121                 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    122                 conn.setRequestMethod("GET");
    123                 // 指定从服务器下载的范围,http请求的头
    124                 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
    125 
    126                 int code = conn.getResponseCode();// 2XX成功 3XX重定向 4XX资源找不到
    127                                                     // 5XX服务器异常
    128                 if (code == 206) {// 206:表示请求部分数据成功
    129                     // 返回服务器端对应数据的输入流
    130                     InputStream is = conn.getInputStream();
    131                     RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
    132 
    133                     /**
    134                      * ☆☆☆☆☆非常重要☆☆☆☆☆ 一定要记得定位文件写的位置
    135                      * 不同线程在文件中(代码开头:创建的空文件)开始写入的位置是不一样的
    136                      */
    137                     raf.seek(startIndex);
    138                     byte[] buffer = new byte[1024 * 1024 * 10];
    139                     int len = -1;
    140                     while ((len = is.read(buffer)) != -1) {
    141                         raf.write(buffer, 0, len);
    142                         filePosition += len;
    143                         RandomAccessFile rafinfo = new RandomAccessFile(file, "rwd");
    144                         rafinfo.write(String.valueOf(filePosition).getBytes());
    145                         rafinfo.close();
    146                     }
    147                     raf.close();
    148                     is.close();
    149 
    150                     System.out.println("线程:" + threadId + "下载完毕了。");
    151 
    152                 }
    153             } catch (Exception e) {
    154                 e.printStackTrace();
    155             } finally {
    156 
    157                 // 三个线程都结束了,下载完毕
    158                 synchronized (MutilDownloader.class) {
    159                     runningThreadCount--;
    160                     if (runningThreadCount == 0) {
    161                         System.out.println("所有的线程都下载完毕了");
    162                         for (int i = 0; i < threadCount; i++) {
    163                             File f = new File(i + getFileName(path) + ".txt");
    164                             System.out.println(f.delete());
    165                         }
    166                     }
    167                 }
    168             }
    169         }
    170     }
    171 
    172     /**
    173      * 获取路径对应的文件名
    174      * 
    175      * @param path
    176      * @return
    177      */
    178     private static String getFileName(String path) {
    179         int beginIndex = path.lastIndexOf("/") + 1;
    180         return path.substring(beginIndex);
    181     }
    182 
    183 }

    需要特别注意的是:RandomAccessFile.seek(startIndex),它是用来定位文件写入的位置

    运行程序,观察Console,如下:

    刷新Java工程项目,如下:

    双击打开test.avi,发现是可以播放的。

  • 相关阅读:
    Linux 查看本地ip
    php 利用debug_backtrace方法跟踪代码调用
    开源镜像站,vmware下载
    robots.txt 让搜索引擎不再收录网站
    PHP 面向对象 final类与final方法
    开源代码
    PHPStorm设置Ctrl+滚轮调整字体大小
    PHP array_chunk() 妙用
    第九节 JavaScript提取行间事件
    第八节 JavaScript函数的定义和执行
  • 原文地址:https://www.cnblogs.com/hebao0514/p/4790941.html
Copyright © 2011-2022 走看看