• Java多线程下载初试


     一、服务端/客户端代码的实现

    服务端配置config

     1 @ConfigurationProperties("storage")
     2 public class StorageProperties {
     3     private String location = "D:\idea_project\upload\src\main\resources\upload-files";
     4 
     5     public String getLocation() {
     6         return location;
     7     }
     8 
     9     public void setLocation(String location) {
    10         this.location = location;
    11     }
    12 }

    服务端Controller

    1 @GetMapping("/files/{filename:.+}")
    2     @ResponseBody
    3     public ResponseEntity<Resource> serveFile(@PathVariable String filename) {
    4         Resource file = storageService.loadAsResource(filename);
    5         return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
    6                 "attachment; filename="" + file.getFilename() + """).body(file);
    7     }

    服务端Service

    1 Path load(String filename);
    2 
    3 Resource loadAsResource(String filename);
     1 package org.wlgzs.upload.service.impl;
     2 
     3 import org.springframework.beans.factory.annotation.Autowired;
     4 import org.springframework.core.io.Resource;
     5 import org.springframework.core.io.UrlResource;
     6 import org.springframework.stereotype.Service;
     7 import org.springframework.util.FileSystemUtils;
     8 import org.springframework.util.StringUtils;
     9 import org.springframework.web.multipart.MultipartFile;
    10 import org.wlgzs.upload.config.StorageProperties;
    11 import org.wlgzs.upload.service.StorageService;
    12 
    13 import java.io.IOException;
    14 import java.net.MalformedURLException;
    15 import java.nio.file.Files;
    16 import java.nio.file.Path;
    17 import java.nio.file.Paths;
    18 import java.nio.file.StandardCopyOption;
    19 import java.util.stream.Stream;
    20 
    21 /**
    22  * @author zsh
    23  * @company wlgzs
    24  * @create 2018-12-15 16:16
    25  * @Describe
    26  */
    27 
    28 @Service
    29 public class FileSystemStorageService implements StorageService {
    30 
    31     private final Path rootLocation;
    32 
    33     @Autowired
    34     public FileSystemStorageService(StorageProperties properties) {
    35         this.rootLocation = Paths.get(properties.getLocation());
    36     }
    37 
    38     @Override
    39     public Path load(String filename) {
    40         return rootLocation.resolve(filename);
    41     }
    42 
    43     @Override
    44     public Resource loadAsResource(String filename) {
    45         try {
    46             Path file = load(filename);
    47             Resource resource = new UrlResource(file.toUri());
    48             if (resource.exists() || resource.isReadable()) {
    49                 return resource;
    50             }
    51             else {
    52                 System.out.println("Could not read file: " + filename);
    53                 //throw new StorageFileNotFoundException("Could not read file: " + filename);
    54 
    55             }
    56         }
    57         catch (MalformedURLException e) {
    58             System.out.println("Could not read file: " + filename);
    59             //throw new StorageFileNotFoundException("Could not read file: " + filename, e);
    60         }
    61         return null;
    62     }
    63 
    64 }

    服务端目录结构

    客户端Main类

     1 import java.util.Scanner;
     2 import java.util.concurrent.TimeUnit;
     3 
     4 /**
     5  * @author zsh
     6  * @site www.qqzsh.top
     7  * @company wlgzs
     8  * @create 2019-05-27 9:03
     9  * @description 主线程启动入口
    10  */
    11 public class Main {
    12     public static void main(String[] args) {
    13         Scanner scanner = new Scanner(System.in);
    14         System.out.println("请输入下载文件的地址,按ENTER结束");
    15         String downpath = scanner.nextLine();
    16         System.out.println("下载的文件名及路径为:"+ MultiPartDownLoad.downLoad(downpath));
    17         try {
    18             System.out.println("下载完成,本窗口5s之后自动关闭");
    19             TimeUnit.SECONDS.sleep(5);
    20         } catch (InterruptedException e) {
    21             e.printStackTrace();
    22         }
    23         System.exit(0);
    24     }
    25 }

    客户端线程池Constans类

     1 import java.util.concurrent.*;
     2 
     3 /**
     4  * @author zsh
     5  * @site www.qqzsh.top
     6  * @company wlgzs
     7  * @create 2019-05-27 8:52
     8  * @description 自定义线程池
     9  */
    10 public class Constans {
    11 
    12     public static final int MAX_THREAD_COUNT = getSystemProcessCount();
    13     private static final int MAX_IMUMPOOLSIZE = MAX_THREAD_COUNT;
    14 
    15     /**
    16      * 自定义线程池
    17      */
    18     private static ExecutorService MY_THREAD_POOL;
    19     /**
    20      * 自定义线程池
    21      */
    22     public static ExecutorService getMyThreadPool(){
    23         if(MY_THREAD_POOL == null){
    24             MY_THREAD_POOL = Executors.newFixedThreadPool(MAX_IMUMPOOLSIZE);
    25         }
    26         return MY_THREAD_POOL;
    27     }
    28 
    29     /**
    30      * 线程池
    31      */
    32     private static ThreadPoolExecutor threadPool;
    33 
    34     /**
    35      * 单例,单任务 线程池
    36      * @return
    37      */
    38     public static ThreadPoolExecutor getThreadPool(){
    39         if(threadPool == null){
    40             threadPool = new ThreadPoolExecutor(MAX_IMUMPOOLSIZE, MAX_IMUMPOOLSIZE, 3, TimeUnit.SECONDS,
    41                     new ArrayBlockingQueue<>(16),
    42                     new ThreadPoolExecutor.CallerRunsPolicy()
    43             );
    44         }
    45         return threadPool;
    46     }
    47 
    48     /**
    49      * 获取服务器cpu核数
    50      * @return
    51      */
    52     private static int getSystemProcessCount(){
    53         return Runtime.getRuntime().availableProcessors();
    54     }
    55 }

    客户端多线程下载类MultiPartDownLoad

      1 import java.io.File;
      2 import java.io.IOException;
      3 import java.io.InputStream;
      4 import java.io.RandomAccessFile;
      5 import java.net.HttpURLConnection;
      6 import java.net.URL;
      7 import java.util.UUID;
      8 import java.util.concurrent.CountDownLatch;
      9 import java.util.concurrent.ExecutorService;
     10 import java.util.concurrent.locks.ReentrantLock;
     11 
     12 /**
     13  * @author zsh
     14  * @site www.qqzsh.top
     15  * @company wlgzs
     16  * @create 2019-05-27 8:53
     17  * @description 多线程下载主程序
     18  */
     19 public class MultiPartDownLoad {
     20     /**
     21      * 线程下载成功标志
     22      */
     23     private static int flag = 0;
     24 
     25     /**
     26      * 服务器请求路径
     27      */
     28     private String serverPath;
     29     /**
     30      * 本地路径
     31      */
     32     private String localPath;
     33     /**
     34      * 线程计数同步辅助
     35      */
     36     private CountDownLatch latch;
     37     /**
     38      * 定长线程池
     39      */
     40     private static ExecutorService threadPool;
     41 
     42     public MultiPartDownLoad(String serverPath, String localPath) {
     43         this.serverPath = serverPath;
     44         this.localPath = localPath;
     45     }
     46 
     47     public boolean executeDownLoad() {
     48         try {
     49             URL url = new URL(serverPath);
     50             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
     51             //设置超时时间
     52             conn.setConnectTimeout(5000);
     53             //设置请求方式
     54             conn.setRequestMethod("GET");
     55             conn.setRequestProperty("Connection", "Keep-Alive");
     56             int code = conn.getResponseCode();
     57             if (code != 200) {
     58                 System.out.println(String.format("无效网络地址:%s", serverPath));
     59                 return false;
     60             }
     61             //服务器返回的数据的长度,实际上就是文件的长度,单位是字节
     62             // int length = conn.getContentLength();  //文件超过2G会有问题
     63             long length = getRemoteFileSize(serverPath);
     64 
     65             System.out.println("远程文件总长度:" + length + "字节(B),"+length/Math.pow(2,20)+"MB");
     66             RandomAccessFile raf = new RandomAccessFile(localPath, "rwd");
     67             //指定创建的文件的长度
     68             raf.setLength(length);
     69             raf.close();
     70             //分割文件
     71             int partCount = Constans.MAX_THREAD_COUNT;
     72             int partSize = (int)(length / partCount);
     73             latch = new CountDownLatch(partCount);
     74             threadPool = Constans.getMyThreadPool();
     75             for (int threadId = 1; threadId <= partCount; threadId++) {
     76                 // 每一个线程下载的开始位置
     77                 long startIndex = (threadId - 1) * partSize;
     78                 // 每一个线程下载的开始位置
     79                 long endIndex = startIndex + partSize - 1;
     80                 if (threadId == partCount) {
     81                     //最后一个线程下载的长度稍微长一点
     82                     endIndex = length;
     83                 }
     84                 System.out.println("线程" + threadId + "下载:" + startIndex + "字节~" + endIndex + "字节");
     85                 threadPool.execute(new DownLoadThread(threadId, startIndex, endIndex, latch));
     86             }
     87             latch.await();
     88             if(flag == 0){
     89                 return true;
     90             }
     91         } catch (Exception e) {
     92             System.out.println(String.format("文件下载失败,文件地址:%s,失败原因:%s", serverPath, e.getMessage()));
     93         }
     94         return false;
     95     }
     96 
     97     /**
     98      * 内部类用于实现下载
     99      */
    100     public class DownLoadThread implements Runnable {
    101 
    102         /**
    103          * 线程ID
    104          */
    105         private int threadId;
    106         /**
    107          * 下载起始位置
    108          */
    109         private long startIndex;
    110         /**
    111          * 下载结束位置
    112          */
    113         private long endIndex;
    114 
    115         private CountDownLatch latch;
    116 
    117         DownLoadThread(int threadId, long startIndex, long endIndex, CountDownLatch latch) {
    118             this.threadId = threadId;
    119             this.startIndex = startIndex;
    120             this.endIndex = endIndex;
    121             this.latch = latch;
    122         }
    123 
    124         @Override
    125         public void run() {
    126             try {
    127                 System.out.println("线程" + threadId + "正在下载...");
    128                 URL url = new URL(serverPath);
    129                 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    130                 conn.setRequestProperty("Connection", "Keep-Alive");
    131                 conn.setRequestMethod("GET");
    132                 //请求服务器下载部分的文件的指定位置
    133                 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
    134                 conn.setConnectTimeout(5000);
    135                 int code = conn.getResponseCode();
    136                 System.out.println("线程" + threadId + "请求返回code=" + code);
    137                 //返回资源
    138                 InputStream is = conn.getInputStream();
    139                 RandomAccessFile raf = new RandomAccessFile(localPath, "rwd");
    140                 //随机写文件的时候从哪个位置开始写
    141                 //定位文件
    142                 raf.seek(startIndex);
    143                 int len;
    144                 byte[] buffer = new byte[1024];
    145                 int realLen = 0;
    146                 while ((len = is.read(buffer)) != -1) {
    147                     realLen += len;
    148                     raf.write(buffer, 0, len);
    149                 }
    150                 System.out.println("线程" + threadId + "下载文件大小=" + realLen/Math.pow(2,20)+"MB");
    151                 is.close();
    152                 raf.close();
    153                 System.out.println("线程" + threadId + "下载完毕");
    154             } catch (Exception e) {
    155                 //线程下载出错
    156                 MultiPartDownLoad.flag = 1;
    157                 System.out.println(e.getMessage());
    158             } finally {
    159                 //计数值减一
    160                 latch.countDown();
    161             }
    162         }
    163     }
    164 
    165     /**
    166      * 内部方法,获取远程文件大小
    167      * @param remoteFileUrl
    168      * @return
    169      * @throws IOException
    170      */
    171     private long getRemoteFileSize(String remoteFileUrl) throws IOException {
    172         long fileSize;
    173         HttpURLConnection httpConnection = (HttpURLConnection) new URL(remoteFileUrl).openConnection();
    174         httpConnection.setRequestMethod("HEAD");
    175         int responseCode = 0;
    176         try {
    177             responseCode = httpConnection.getResponseCode();
    178         } catch (IOException e) {
    179             e.printStackTrace();
    180         }
    181         if (responseCode >= 400) {
    182             System.out.println("Web服务器响应错误!请稍后重试");
    183             return 0;
    184         }
    185         String sHeader;
    186         for (int i = 1;; i++) {
    187             sHeader = httpConnection.getHeaderFieldKey(i);
    188             if ("Content-Length".equals(sHeader)) {
    189                 fileSize = Long.parseLong(httpConnection.getHeaderField(sHeader));
    190                 break;
    191             }
    192         }
    193         return fileSize;
    194     }
    195 
    196     /**
    197      * 下载文件执行器
    198      * @param serverPath
    199      * @return
    200      */
    201     public synchronized static String downLoad(String serverPath) {
    202         ReentrantLock lock = new ReentrantLock();
    203         lock.lock();
    204 
    205         String[] names = serverPath.split("\.");
    206         if (names.length <= 0) {
    207             return null;
    208         }
    209         String fileTypeName = names[names.length - 1];
    210         String localPath = String.format("%s.%s", new File("").getAbsolutePath()+"\"+UUID.randomUUID(),fileTypeName);
    211         MultiPartDownLoad m = new MultiPartDownLoad(serverPath, localPath);
    212         long startTime = System.currentTimeMillis();
    213         boolean flag = false;
    214         try{
    215             flag = m.executeDownLoad();
    216             long endTime = System.currentTimeMillis();
    217             if(flag){
    218                 System.out.println("文件下载结束,共耗时" + (endTime - startTime)+ "ms");
    219                 return localPath;
    220             }
    221             System.out.println("文件下载失败");
    222             return null;
    223         }catch (Exception ex){
    224             System.out.println(ex.getMessage());
    225             return null;
    226         }finally {
    227             // 重置 下载状态
    228             MultiPartDownLoad.flag = 0;
    229             if(!flag){
    230                 File file = new File(localPath);
    231                 file.delete();
    232             }
    233             lock.unlock();
    234         }
    235     }
    236 }

    客户端目录结构

    下载效果:

    二、核心部分

      多线程下载不仅需要客户端的支持,也需要服务端的支持。 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);Range响应头是多线程下载分割的关键所在。

      下载思路:首先判断下载文件大小,配合多线程分割定制http请求数量和请求内容,响应到写入到RandomAccessFile指定位置中。在俗点就是大的http分割成一个个小的http请求,相当于每次请求一个网页。RandomAccessFile文件随机类,可以向文件写入指定位置的流信息。

    三、将Java类打包成jar(idea)

    1、创建空jar

    2、将.class文件加入jar

    此时要注意,如果类存在包名,需要一级一级建立与之对应的包名

     3、创建Manifest

    Manifest-Version: 1.0
    Main-Class: Main

    4、build jar包

    5、如果出现找不到或无法加载主类,就看下Main-Class是否为完整包名。

    四、在无Java环境的win上执行bat

    目录

    bat脚本

    start jreinjava -jar download.jar

    五、完整程序地址

     链接: https://pan.baidu.com/s/1Yy9fnRut9gLo5ENgtoElpA 提取码: mh3c 复制这段内容后打开百度网盘手机App,操作更方便哦

  • 相关阅读:
    Android学习小Demo一个显示行线的自定义EditText
    Android中自定义checkbox样式
    android圆角View实现及不同版本这间的兼容
    android下大文件分割上传
    drwtsn32.exe 遇到问题须要关闭。我们对此引起的不便表示抱歉
    【分享】深入浅出WPF全系列教程及源码
    iOS国际化时遇到的错误:read failed: the data couldn&#39;t be read because it isn&#39;t in the correct format.
    void及void指针含义的深刻解析
    堆和栈的差别(转过无数次的文章)
    sizeof,终极无惑(上)
  • 原文地址:https://www.cnblogs.com/zsh-blogs/p/10931747.html
Copyright © 2020-2023  润新知