对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题。所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率。
本文是基于 springboot + vue 实现的文件上传,本文主要介绍服务端实现文件上传的步骤及代码实现,vue的实现步骤及实现请移步本人的另一篇文章
上传分步:
本人分析上传总共分为:
-
检查文件是否已上传,如已上传可实现秒传
-
创建临时文件(._tmp)和上传的配置文件(.conf)
-
使用RandomAccessFile获取临时文件
-
调用RandomAccessFile的getChannel()方法,打开文件通道 FileChannel
-
获取当前是第几个分块,计算文件的最后偏移量
-
获取当前文件分块的字节数组,用于获取文件字节长度
-
使用文件通道FileChannel类的 map()方法创建直接字节缓冲器 MappedByteBuffer
-
将分块的字节数组放入到当前位置的缓冲区内 mappedByteBuffer.put(byte[] b)
-
释放缓冲区
-
检查文件是否全部完成上传,如上传完成将临时文件名为正式文件名
直接上代码
public class FlieChunkUtils {
/**
* 分块上传
* 第一步:获取RandomAccessFile,随机访问文件类的对象
* 第二步:调用RandomAccessFile的getChannel()方法,打开文件通道 FileChannel
* 第三步:获取当前是第几个分块,计算文件的最后偏移量
* 第四步:获取当前文件分块的字节数组,用于获取文件字节长度
* 第五步:使用文件通道FileChannel类的 map()方法创建直接字节缓冲器 MappedByteBuffer
* 第六步:将分块的字节数组放入到当前位置的缓冲区内 mappedByteBuffer.put(byte[] b);
* 第七步:释放缓冲区
* 第八步:检查文件是否全部完成上传
*
* @param param
* @return
* @throws Exception
*/
public static ApiResult uploadByMappedByteBuffer(MultipartFileParam param) throws Exception {
if (param.getIdentifier() == null || "".equals(param.getIdentifier())) {
param.setIdentifier(UUID.randomUUID().toString());
}
// 判断是否上传
if (ObjectUtil.isEmpty(param.getFile())) {
return checkUploadStatus(param);
}
// 文件名称
String fileName = getFileName(param);
// 临时文件名称
String tempFileName = param.getIdentifier() + fileName.substring(fileName.lastIndexOf(".")) + "_tmp";
// 获取文件路径
String filePath = getUploadPath(param);
// 创建文件夹
FileUploadUtils.getAbsoluteFile(filePath, fileName);
// 创建临时文件
File tempFile = new File(filePath, tempFileName);
//第一步 获取RandomAccessFile,随机访问文件类的对象
RandomAccessFile raf = RandomAccessFileUitls.getModelRW(tempFile);
//第二步 调用RandomAccessFile的getChannel()方法,打开文件通道 FileChannel
FileChannel fileChannel = raf.getChannel();
//第三步 获取当前是第几个分块,计算文件的最后偏移量
long offset = (param.getChunkNumber() - 1) * param.getChunkSize();
//第四步 获取当前文件分块的字节数组,用于获取文件字节长度
byte[] fileData = param.getFile().getBytes();
//第五步 使用文件通道FileChannel类的 map()方法创建直接字节缓冲器 MappedByteBuffer
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
//第六步 将分块的字节数组放入到当前位置的缓冲区内 mappedByteBuffer.put(byte[] b)
mappedByteBuffer.put(fileData);
//第七步 释放缓冲区
freeMappedByteBuffer(mappedByteBuffer);
fileChannel.close();
raf.close();
//第八步 检查文件是否全部完成上传
ApiResult