java对MultipartFile操作,文件下载
1、把MultipartFile转File
/**
* 通过IO流转换(避免临时文件残留)
*
* @param multipartFile 上传文件对象
* @return 转换后的File对象
* @throws IOException
*/
public static File convertByStream(MultipartFile multipartFile) throws IOException {
InputStream inputStream = multipartFile.getInputStream();
File file = new File(multipartFile.getOriginalFilename());
try (OutputStream os = new FileOutputStream(file)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
}
inputStream.close();
return file;
}
2、把MultipartFile切割成多个小文件
把一个大文件切割成很多个小文件时用到。
下面例子是按照每个文件50KB切割。
/**
* 切割MultipartFile
*
* @param file
* @param chunkSize
* @return
* @throws IOException
*/
private static final Integer chunkSize = 5 * 1024*10; // 50KB分片
public static List<MultipartFile> splitMultipartFile(MultipartFile file, int chunkSize) throws IOException {
List<MultipartFile> chunks = new ArrayList<>();
byte[] buffer = new byte[chunkSize];
try (InputStream inputStream = file.getInputStream()) {
int bytesRead;
int chunkIndex = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) {
byte[] chunkData = Arrays.copyOf(buffer, bytesRead);
MultipartFile chunk = new MockMultipartFile(
"chunk_" + chunkIndex,
file.getOriginalFilename() + ".part" + chunkIndex,
file.getContentType(),
chunkData
);
chunks.add(chunk);
chunkIndex++;
}
}
return chunks;
}
3、MultipartFile切割的小文件保存到磁盘
private static final Integer chunkSize = 5 * 1024 * 10; // 50KB分片
private static final String destDir = "D:\\merge"; // 分片后文件保存目录
/**
* 对上传的文件进行切割
* @param file
* @throws Exception
*/
@PostMapping("/fileCutting")
public void fileCutting(MultipartFile file) throws Exception {
File sourceFile = convertByStream(file);
try (RandomAccessFile raf = new RandomAccessFile(sourceFile, "r");
FileChannel channel = raf.getChannel()) {
long fileSize = sourceFile.length();
int chunkCount = (int) Math.ceil((double) fileSize / chunkSize);
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
for (int i = 0; i < chunkCount; i++) {
final int index = i;
executor.execute(() -> {
try {
long startPos = (long) index * chunkSize;
long endPos = Math.min(startPos + chunkSize, fileSize);
String chunkPath = destDir + "/" + sourceFile.getName() + ".part" + index;
try (FileOutputStream fos = new FileOutputStream(chunkPath);
FileChannel outChannel = fos.getChannel()) {
channel.transferTo(startPos, endPos - startPos, outChannel);
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
4、切割后的文件进行合并
private static final String destDir = "D:\\merge"; // 分片后文件保存目录
@PostMapping("/fileMerger")
public void fileMerger() throws Exception {
File dir = new File(destDir);
List<File> chunkFiles = Arrays.asList(dir.listFiles());
//指定生成的文件路径
File outputFile = new File("D:\\merge\\xxx.pdf");
/**
* 按文件名自然排序
* 这个地方一定要按照切割文件的顺序
*/
chunkFiles.sort(Comparator.comparing(File::getName));
ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
try (FileOutputStream fos = new FileOutputStream(outputFile);
BufferedOutputStream bos = new BufferedOutputStream(fos)) {
List<Future<Integer>> futures = new ArrayList<>();
for (File chunk : chunkFiles) {
futures.add(executor.submit(new MergeTask(chunk, bos)));
}
// 等待所有任务完成
for (Future<Integer> future : futures) {
future.get();
}
} catch (ExecutionException e) {
throw new RuntimeException(e);
} finally {
executor.shutdown();
}
}
private static class MergeTask implements Callable<Integer> {
private final File chunkFile;
private final BufferedOutputStream outputStream;
MergeTask(File chunkFile, BufferedOutputStream outputStream) {
this.chunkFile = chunkFile;
this.outputStream = outputStream;
}
@Override
public Integer call() throws Exception {
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
try (FileInputStream fis = new FileInputStream(chunkFile);
BufferedInputStream bis = new BufferedInputStream(fis)) {
while ((bytesRead = bis.read(buffer)) != -1) {
synchronized (outputStream) {
outputStream.write(buffer, 0, bytesRead);
}
}
}
return (int) chunkFile.length();
}
}
5、SpringBoot实现文件下载
/**
* @param response
* @功能描述 下载文件:将输入流中的数据循环写入到响应输出流中,而不是一次性读取到内存
*/
@RequestMapping("/downloadLocal")
public void downloadLocal(HttpServletResponse response) throws IOException {
//要下载的文件
File file = new File("D:\\xxx.docx");
// 读到流中
InputStream inputStream = new FileInputStream(file);// 文件的存放路径
response.reset();
response.setContentType("application/octet-stream");
String filename = file.getName();
//Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
//attachment 表示以附件方式下载 inline 表示在线打开
//filename表示文件的默认名称,因为网络传输只支持URL编码的相关支付,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
// 告知浏览器文件的大小
response.addHeader("Content-Length", "" + file.length());
ServletOutputStream outputStream = response.getOutputStream();
byte[] b = new byte[1024];
int len;
//从输入流中读取一定数量的字节,并将其存储在缓冲区字节数组中,读到末尾返回-1
while ((len = inputStream.read(b)) > 0) {
outputStream.write(b, 0, len);
}
inputStream.close();
outputStream.close();
}