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 jre\bin\java -jar download.jar

五、完整程序地址

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

转载于:https://www.cnblogs.com/zsh-blogs/p/10931747.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值