java 多线程下载器_Java多线程下载初试

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

服务端配置config

1 @ConfigurationProperties("storage")2 public classStorageProperties {3 private String location = "D:\\idea_project\\upload\\src\\main\\resources\\upload-files";4

5 publicString getLocation() {6 returnlocation;7 }8

9 public voidsetLocation(String location) {10 this.location =location;11 }12 }

服务端Controller

1 @GetMapping("/files/{filename:.+}")2 @ResponseBody3 public ResponseEntityserveFile(@PathVariable String filename) {4 Resource file =storageService.loadAsResource(filename);5 returnResponseEntity.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 packageorg.wlgzs.upload.service.impl;2

3 importorg.springframework.beans.factory.annotation.Autowired;4 importorg.springframework.core.io.Resource;5 importorg.springframework.core.io.UrlResource;6 importorg.springframework.stereotype.Service;7 importorg.springframework.util.FileSystemUtils;8 importorg.springframework.util.StringUtils;9 importorg.springframework.web.multipart.MultipartFile;10 importorg.wlgzs.upload.config.StorageProperties;11 importorg.wlgzs.upload.service.StorageService;12

13 importjava.io.IOException;14 importjava.net.MalformedURLException;15 importjava.nio.file.Files;16 importjava.nio.file.Path;17 importjava.nio.file.Paths;18 importjava.nio.file.StandardCopyOption;19 importjava.util.stream.Stream;20

21 /**

22 *@authorzsh23 * @company wlgzs24 * @create 2018-12-15 16:1625 * @Describe26 */

27

28 @Service29 public class FileSystemStorageService implementsStorageService {30

31 private finalPath rootLocation;32

33 @Autowired34 publicFileSystemStorageService(StorageProperties properties) {35 this.rootLocation =Paths.get(properties.getLocation());36 }37

38 @Override39 publicPath load(String filename) {40 returnrootLocation.resolve(filename);41 }42

43 @Override44 publicResource loadAsResource(String filename) {45 try{46 Path file =load(filename);47 Resource resource = newUrlResource(file.toUri());48 if (resource.exists() ||resource.isReadable()) {49 returnresource;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 }

服务端目录结构

39f09d855a1b10b40ebf0ebd7ed5b9bf.png

客户端Main类

1 importjava.util.Scanner;2 importjava.util.concurrent.TimeUnit;3

4 /**

5 *@authorzsh6 * @site www.qqzsh.top7 * @company wlgzs8 * @create 2019-05-27 9:039 * @description 主线程启动入口10 */

11 public classMain {12 public static voidmain(String[] args) {13 Scanner scanner = newScanner(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 *@authorzsh5 * @site www.qqzsh.top6 * @company wlgzs7 * @create 2019-05-27 8:528 * @description 自定义线程池9 */

10 public classConstans {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 staticExecutorService MY_THREAD_POOL;19 /**

20 * 自定义线程池21 */

22 public staticExecutorService getMyThreadPool(){23 if(MY_THREAD_POOL == null){24 MY_THREAD_POOL =Executors.newFixedThreadPool(MAX_IMUMPOOLSIZE);25 }26 returnMY_THREAD_POOL;27 }28

29 /**

30 * 线程池31 */

32 private staticThreadPoolExecutor threadPool;33

34 /**

35 * 单例,单任务 线程池36 *@return

37 */

38 public staticThreadPoolExecutor getThreadPool(){39 if(threadPool == null){40 threadPool = new ThreadPoolExecutor(MAX_IMUMPOOLSIZE, MAX_IMUMPOOLSIZE, 3, TimeUnit.SECONDS,41 new ArrayBlockingQueue<>(16),42 newThreadPoolExecutor.CallerRunsPolicy()43 );44 }45 returnthreadPool;46 }47

48 /**

49 * 获取服务器cpu核数50 *@return

51 */

52 private static intgetSystemProcessCount(){53 returnRuntime.getRuntime().availableProcessors();54 }55 }

客户端多线程下载类MultiPartDownLoad

1 importjava.io.File;2 importjava.io.IOException;3 importjava.io.InputStream;4 importjava.io.RandomAccessFile;5 importjava.net.HttpURLConnection;6 importjava.net.URL;7 importjava.util.UUID;8 importjava.util.concurrent.CountDownLatch;9 importjava.util.concurrent.ExecutorService;10 importjava.util.concurrent.locks.ReentrantLock;11

12 /**

13 *@authorzsh14 * @site www.qqzsh.top15 * @company wlgzs16 * @create 2019-05-27 8:5317 * @description 多线程下载主程序18 */

19 public classMultiPartDownLoad {20 /**

21 * 线程下载成功标志22 */

23 private static int flag = 0;24

25 /**

26 * 服务器请求路径27 */

28 privateString serverPath;29 /**

30 * 本地路径31 */

32 privateString localPath;33 /**

34 * 线程计数同步辅助35 */

36 privateCountDownLatch latch;37 /**

38 * 定长线程池39 */

40 private staticExecutorService threadPool;41

42 publicMultiPartDownLoad(String serverPath, String localPath) {43 this.serverPath =serverPath;44 this.localPath =localPath;45 }46

47 public booleanexecuteDownLoad() {48 try{49 URL url = newURL(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 = newCountDownLatch(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(newDownLoadThread(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 implementsRunnable {101

102 /**

103 * 线程ID104 */

105 private intthreadId;106 /**

107 * 下载起始位置108 */

109 private longstartIndex;110 /**

111 * 下载结束位置112 */

113 private longendIndex;114

115 privateCountDownLatch latch;116

117 DownLoadThread(int threadId, long startIndex, longendIndex, CountDownLatch latch) {118 this.threadId =threadId;119 this.startIndex =startIndex;120 this.endIndex =endIndex;121 this.latch =latch;122 }123

124 @Override125 public voidrun() {126 try{127 System.out.println("线程" + threadId + "正在下载...");128 URL url = newURL(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 intlen;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 *@paramremoteFileUrl168 *@return

169 *@throwsIOException170 */

171 private long getRemoteFileSize(String remoteFileUrl) throwsIOException {172 longfileSize;173 HttpURLConnection httpConnection = (HttpURLConnection) newURL(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 returnfileSize;194 }195

196 /**

197 * 下载文件执行器198 *@paramserverPath199 *@return

200 */

201 public synchronized staticString downLoad(String serverPath) {202 ReentrantLock lock = newReentrantLock();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 = newMultiPartDownLoad(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 returnlocalPath;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 = newFile(localPath);231 file.delete();232 }233 lock.unlock();234 }235 }236 }

客户端目录结构

d6834e2f89d3b2746bf85d9c4c0d14f4.png

下载效果:

59f0c1e6941eee28a313be74385ef000.png

二、核心部分

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

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

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

1、创建空jar

494e65d8666b5a816bfb9820d23d0aae.png

2、将.class文件加入jar

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

9dc1d963e029b5cb99785de0113f08f6.png

92b15d84b8cd945ea206aa807298a124.png

3、创建Manifest

2aa28a9375f886052b3d37c3bf333df9.png

Manifest-Version: 1.0Main-Class: Main

4、build jar包

b7ef1756fb0ab4a4c8ffeabdd4e1b3bc.png

521090dfcb0df0dcc57ce74b102255dc.png

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

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

目录

d33960ae498be53c961adaa93874947b.png

bat脚本

start jre\bin\java -jar download.jar

五、完整程序地址

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值