大文件上传–分片上传

分片上传,就是将所要文件,按照一定的大小,将整个文件分割为多个数据块来进行分别上传,上传完再由服务端对所有上传的文件进行汇总成原始的文件。

主要使用场景:

  • 网络不好:上传失败的时候,可以对失败的part进行独立的重试,而不需要上传其他部分
  • 断点续传:中途上传暂停,可以从上次上传的part继续
  • 加速上传:上传的文件很大时,可以分part加速上传
  • 流式上传:不确定上传文件大小时候开始上传
  • 文件较大:文件大,默认采用分片上传

大致流程:

  • 将上传的文件按照规则进行分割成大小统一的数据块
  • 初始化一个上传任务,返回本次分片上传唯一标识
  • 按照一定的策略发送各个分片数据
  • 发生完成后,服务端根据判断上传数据是否完整,如果完整,则组合得到原始文件,并返回上传路径

准备

采用前后端分离完成上传功能

前端部分

后端部分

方案一:RandomAccessFile读取文件,通过设计偏移量进行文件拼装

1
2
3
4
5
6
7
8
9
10
11
12
// 更具参数创建或者获取已经上传过分片的文件
File tmpFile = super.createTmpFile(param);
// 创建随机文件流
RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw");
//这个必须与前端设定的值一致
long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024 : param.getChunkSize();
long offset = chunkSize * param.getChunk();
//定位到该分片的偏移量
accessTmpFile.seek(offset);
//写入该分片数据
accessTmpFile.write(param.getFile().getBytes());
accessTmpFile.close()

方案二:RandomAccessFile读取文件,然后使用MappedByteBuffer进行文件拼装(效率更高)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 更具参数创建或者获取已经上传过分片的文件
File tmpFile = super.createTmpFile(param);
// 创建随机文件流
RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw");
// 获取文件通道
FileChannel fileChannel = tempRaf.getChannel();

// 获取分片长度
long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024 : param.getChunkSize();
//写入该分片数据
long offset = chunkSize * param.getChunk();
byte[] fileData = param.getFile().getBytes();

// 通过文件通道构建MapperByteBuffer
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
mappedByteBuffer.put(fileData);

fileChannel.close();
accessTmpFile.close();

FileChannel

FileChannel 提供了一种通过通道来访问文件的方式,它可以通过带参数 position(int) 方法定位到文件的任意位置开始进行操作,还能够将文件映射到直接内存,提高大文件的访问效率
通道获取
  • FileInputStream/FileOutputStream
1
2
3
4
FileInputStream fis = new FileInputStream(new File("src/test11/hello.txt"));
FileChannel channel = fis.getChannel();
FileOutputStream fos = new FileOutputStream(new File(""));
FileChannel channel = fos.getChannel();
  • RandomAccessFile
1
2
3
RandomAccessFile raf = new RandomAccessFile(new File(""), "rw");
FileChannel channel = raf.getChannel();

  • FileChannel.open()
1
FileChannel fileChannel = FileChannel.open(Paths.get("hello.txt"))
读取数据
写入数据

MappedByteBuffer