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

服务端配置config

 @ConfigurationProperties("storage")
public class StorageProperties {
private String location = "D:\\idea_project\\upload\\src\\main\\resources\\upload-files"; public String getLocation() {
return location;
} public void setLocation(String location) {
this.location = location;
}
}

服务端Controller

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

服务端Service

 Path load(String filename);

 Resource loadAsResource(String filename);
 package org.wlgzs.upload.service.impl;

 import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.wlgzs.upload.config.StorageProperties;
import org.wlgzs.upload.service.StorageService; import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.stream.Stream; /**
* @author zsh
* @company wlgzs
* @create 2018-12-15 16:16
* @Describe
*/ @Service
public class FileSystemStorageService implements StorageService { private final Path rootLocation; @Autowired
public FileSystemStorageService(StorageProperties properties) {
this.rootLocation = Paths.get(properties.getLocation());
} @Override
public Path load(String filename) {
return rootLocation.resolve(filename);
} @Override
public Resource loadAsResource(String filename) {
try {
Path file = load(filename);
Resource resource = new UrlResource(file.toUri());
if (resource.exists() || resource.isReadable()) {
return resource;
}
else {
System.out.println("Could not read file: " + filename);
//throw new StorageFileNotFoundException("Could not read file: " + filename); }
}
catch (MalformedURLException e) {
System.out.println("Could not read file: " + filename);
//throw new StorageFileNotFoundException("Could not read file: " + filename, e);
}
return null;
} }

服务端目录结构

客户端Main类

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

客户端线程池Constans类

 import java.util.concurrent.*;

 /**
* @author zsh
* @site www.qqzsh.top
* @company wlgzs
* @create 2019-05-27 8:52
* @description 自定义线程池
*/
public class Constans { public static final int MAX_THREAD_COUNT = getSystemProcessCount();
private static final int MAX_IMUMPOOLSIZE = MAX_THREAD_COUNT; /**
* 自定义线程池
*/
private static ExecutorService MY_THREAD_POOL;
/**
* 自定义线程池
*/
public static ExecutorService getMyThreadPool(){
if(MY_THREAD_POOL == null){
MY_THREAD_POOL = Executors.newFixedThreadPool(MAX_IMUMPOOLSIZE);
}
return MY_THREAD_POOL;
} /**
* 线程池
*/
private static ThreadPoolExecutor threadPool; /**
* 单例,单任务 线程池
* @return
*/
public static ThreadPoolExecutor getThreadPool(){
if(threadPool == null){
threadPool = new ThreadPoolExecutor(MAX_IMUMPOOLSIZE, MAX_IMUMPOOLSIZE, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(16),
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
return threadPool;
} /**
* 获取服务器cpu核数
* @return
*/
private static int getSystemProcessCount(){
return Runtime.getRuntime().availableProcessors();
}
}

客户端多线程下载类MultiPartDownLoad

 import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.ReentrantLock; /**
* @author zsh
* @site www.qqzsh.top
* @company wlgzs
* @create 2019-05-27 8:53
* @description 多线程下载主程序
*/
public class MultiPartDownLoad {
/**
* 线程下载成功标志
*/
private static int flag = 0; /**
* 服务器请求路径
*/
private String serverPath;
/**
* 本地路径
*/
private String localPath;
/**
* 线程计数同步辅助
*/
private CountDownLatch latch;
/**
* 定长线程池
*/
private static ExecutorService threadPool; public MultiPartDownLoad(String serverPath, String localPath) {
this.serverPath = serverPath;
this.localPath = localPath;
} public boolean executeDownLoad() {
try {
URL url = new URL(serverPath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置超时时间
conn.setConnectTimeout(5000);
//设置请求方式
conn.setRequestMethod("GET");
conn.setRequestProperty("Connection", "Keep-Alive");
int code = conn.getResponseCode();
if (code != 200) {
System.out.println(String.format("无效网络地址:%s", serverPath));
return false;
}
//服务器返回的数据的长度,实际上就是文件的长度,单位是字节
// int length = conn.getContentLength(); //文件超过2G会有问题
long length = getRemoteFileSize(serverPath); System.out.println("远程文件总长度:" + length + "字节(B),"+length/Math.pow(2,20)+"MB");
RandomAccessFile raf = new RandomAccessFile(localPath, "rwd");
//指定创建的文件的长度
raf.setLength(length);
raf.close();
//分割文件
int partCount = Constans.MAX_THREAD_COUNT;
int partSize = (int)(length / partCount);
latch = new CountDownLatch(partCount);
threadPool = Constans.getMyThreadPool();
for (int threadId = 1; threadId <= partCount; threadId++) {
// 每一个线程下载的开始位置
long startIndex = (threadId - 1) * partSize;
// 每一个线程下载的开始位置
long endIndex = startIndex + partSize - 1;
if (threadId == partCount) {
//最后一个线程下载的长度稍微长一点
endIndex = length;
}
System.out.println("线程" + threadId + "下载:" + startIndex + "字节~" + endIndex + "字节");
threadPool.execute(new DownLoadThread(threadId, startIndex, endIndex, latch));
}
latch.await();
if(flag == 0){
return true;
}
} catch (Exception e) {
System.out.println(String.format("文件下载失败,文件地址:%s,失败原因:%s", serverPath, e.getMessage()));
}
return false;
} /**
* 内部类用于实现下载
*/
public class DownLoadThread implements Runnable { /**
* 线程ID
*/
private int threadId;
/**
* 下载起始位置
*/
private long startIndex;
/**
* 下载结束位置
*/
private long endIndex; private CountDownLatch latch; DownLoadThread(int threadId, long startIndex, long endIndex, CountDownLatch latch) {
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.latch = latch;
} @Override
public void run() {
try {
System.out.println("线程" + threadId + "正在下载...");
URL url = new URL(serverPath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestMethod("GET");
//请求服务器下载部分的文件的指定位置
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
conn.setConnectTimeout(5000);
int code = conn.getResponseCode();
System.out.println("线程" + threadId + "请求返回code=" + code);
//返回资源
InputStream is = conn.getInputStream();
RandomAccessFile raf = new RandomAccessFile(localPath, "rwd");
//随机写文件的时候从哪个位置开始写
//定位文件
raf.seek(startIndex);
int len;
byte[] buffer = new byte[1024];
int realLen = 0;
while ((len = is.read(buffer)) != -1) {
realLen += len;
raf.write(buffer, 0, len);
}
System.out.println("线程" + threadId + "下载文件大小=" + realLen/Math.pow(2,20)+"MB");
is.close();
raf.close();
System.out.println("线程" + threadId + "下载完毕");
} catch (Exception e) {
//线程下载出错
MultiPartDownLoad.flag = 1;
System.out.println(e.getMessage());
} finally {
//计数值减一
latch.countDown();
}
}
} /**
* 内部方法,获取远程文件大小
* @param remoteFileUrl
* @return
* @throws IOException
*/
private long getRemoteFileSize(String remoteFileUrl) throws IOException {
long fileSize;
HttpURLConnection httpConnection = (HttpURLConnection) new URL(remoteFileUrl).openConnection();
httpConnection.setRequestMethod("HEAD");
int responseCode = 0;
try {
responseCode = httpConnection.getResponseCode();
} catch (IOException e) {
e.printStackTrace();
}
if (responseCode >= 400) {
System.out.println("Web服务器响应错误!请稍后重试");
return 0;
}
String sHeader;
for (int i = 1;; i++) {
sHeader = httpConnection.getHeaderFieldKey(i);
if ("Content-Length".equals(sHeader)) {
fileSize = Long.parseLong(httpConnection.getHeaderField(sHeader));
break;
}
}
return fileSize;
} /**
* 下载文件执行器
* @param serverPath
* @return
*/
public synchronized static String downLoad(String serverPath) {
ReentrantLock lock = new ReentrantLock();
lock.lock(); String[] names = serverPath.split("\\.");
if (names.length <= 0) {
return null;
}
String fileTypeName = names[names.length - 1];
String localPath = String.format("%s.%s", new File("").getAbsolutePath()+"\\"+UUID.randomUUID(),fileTypeName);
MultiPartDownLoad m = new MultiPartDownLoad(serverPath, localPath);
long startTime = System.currentTimeMillis();
boolean flag = false;
try{
flag = m.executeDownLoad();
long endTime = System.currentTimeMillis();
if(flag){
System.out.println("文件下载结束,共耗时" + (endTime - startTime)+ "ms");
return localPath;
}
System.out.println("文件下载失败");
return null;
}catch (Exception ex){
System.out.println(ex.getMessage());
return null;
}finally {
// 重置 下载状态
MultiPartDownLoad.flag = 0;
if(!flag){
File file = new File(localPath);
file.delete();
}
lock.unlock();
}
}
}

客户端目录结构

下载效果:

二、核心部分

  多线程下载不仅需要客户端的支持,也需要服务端的支持。 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,操作更方便哦

Java多线程下载初试的更多相关文章

  1. JAVA多线程下载网络文件

    JAVA多线程下载网络文件,开启多个线程,同时下载网络文件.   源码如下:(点击下载 MultiThreadDownload.java) import java.io.InputStream; im ...

  2. java多线程下载和断点续传

    java多线程下载和断点续传,示例代码只实现了多线程,断点只做了介绍.但是实际测试结果不是很理想,不知道是哪里出了问题.所以贴上来请高手修正. [Java]代码 import java.io.File ...

  3. java 多线程下载功能

    import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; impo ...

  4. java 多线程下载文件 以及URLConnection和HttpURLConnection的区别

    使用 HttpURLConnection 实现多线程下载文件 注意GET大写//http public class MultiThreadDownload { public static void m ...

  5. Java多线程下载文件

    package com.test.download;   import java.io.File; import java.io.InputStream; import java.io.RandomA ...

  6. Java多线程下载器FileDownloader(支持断点续传、代理等功能)

    前言 在我的任务清单中,很早就有了一个文件下载器,但一直忙着没空去写.最近刚好放假,便抽了些时间完成了下文中的这个下载器. 介绍 同样的,还是先上效果图吧. Jar包地址位于 FileDownload ...

  7. Java多线程下载分析

    为什么要多线程下载 俗话说要以终为始,那么我们首先要明确多线程下载的目标是什么,不外乎是为了更快的下载文件.那么问题来了,多线程下载文件相比于单线程是不是更快? 对于这个问题可以看下图. 横坐标是线程 ...

  8. java 多线程下载文件并实时计算下载百分比(断点续传)

    多线程下载文件 多线程同时下载文件即:在同一时间内通过多个线程对同一个请求地址发起多个请求,将需要下载的数据分割成多个部分,同时下载,每个线程只负责下载其中的一部分,最后将每一个线程下载的部分组装起来 ...

  9. java多线程下载网络图片

    import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.BufferedReader ...

随机推荐

  1. Python-20-异常处理

    一.什么是异常 异常就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常,程序的运行也随之终止) 常用异常: AttributeError 试图访问一个对 ...

  2. django使用pyecharts(6)----django加入echarts_增量更新_定长_坐标轴定长

    六.Django 前后端分离_定时增量更新图表(坐标轴定长) 1.安装 djangorestframework linux pip3 install djangorestframework windo ...

  3. python离线安装外部库(第三方库)

    在官网下好外部库,解压后,点击解压后的文件夹,按住shift 右击在命令行中执行 输入 python setup.py install

  4. 【Linux】一步一步学Linux——虚拟机安装和卸载(05)

    目录 00. 目录 01. Workstation Pro 15.0安装简介 02. Windows 主机上安装 Workstation Pro 15.0 03. Linux 主机上安装 Workst ...

  5. 集合并卷积的三种求法(分治乘法,快速莫比乌斯变换(FMT),快速沃尔什变换(FWT))

    也许更好的阅读体验 本文主要内容是对武汉市第二中学吕凯风同学的论文<集合幂级数的性质与应用及其快速算法>的理解 定义 集合幂级数 为了更方便的研究集合的卷积,引入集合幂级数的概念 集合幂级 ...

  6. windows安装docker,快捷启动方式无法启动

    1.在双击“Docker Quickstart Terminal”时弹出缺少快捷方式,截图如下 2.单机快捷方式查看属性,发现配置的git位置是有问题的 现在只需要把git的正确地址配置好就可以了 现 ...

  7. winform 替换word文档中的字段(包含图片添加),生成导出PDF文件(也可是word文件)

    1.先打开你需要替换的word文档,在想要后续更换字段值的地方添加“书签”. 2.将模板文档存放在 程序的Debug文件下. 3.生成文件的按钮点击事件 代码: string templatePath ...

  8. ajax中的事件

    blur : 当光标移开时(点击)触发 change : 当光标移开并且文本框中的内容和上一次不一致时(点击)触发

  9. nginx热加载、热升级、回滚

    修改完配置文件后使用 nginx -s reload 命令进行热加载 编译好新的 nginx 二进制文件后,运行nginx 开启nginx服务,然后使用 kill -USR2 新的nginx_mast ...

  10. python day3 int,str,list类型补充

    目录 python day 3 1. int类小知识点 2. str类小知识点 3. list类小知识点 python day 3 (学习资料来自老男孩教育) 2019/10/06 1. int类小知 ...