背景
项目组自己搭建的文件服务器,要支持文件切片上传、断点续传能力
下面是个简单的demo
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileClient {
// 文件分片
public static void sliceFile() {
File file = new File("D:\\Test_report.docx");
long totalSize = file.length();
System.out.println("文件总大小:" + totalSize);
// 文件总大小:169808
// 设置单个分片大小,为了便于调试,做成25kb一个切片
long chunkSize = 25600;
// 判断总分片数量
long totalChunks = totalSize/chunkSize + (totalSize%chunkSize == 0 ? 0 : 1);
for (int i = 1; i <= totalChunks; i ++) {
File sliceFile = fileSlice(file, chunkSize, (int) totalChunks, i);
System.out.println(sliceFile.getAbsolutePath() + "======" + sliceFile.length());
}
//D:\1.1-code\tools_service\Test_report.docx_1======25600
//D:\1.1-code\tools_service\Test_report.docx_2======25600
//D:\1.1-code\tools_service\Test_report.docx_3======25600
//D:\1.1-code\tools_service\Test_report.docx_4======25600
//D:\1.1-code\tools_service\Test_report.docx_5======25600
//D:\1.1-code\tools_service\Test_report.docx_6======25600
//D:\1.1-code\tools_service\Test_report.docx_7======16208
}
public static void main(String[] args) {
//sliceFile();
assembleFile();
}
// 文件合并
public static void assembleFile() {
try {
// 设定合并后的文件
File resultFile = new File("D:\\1.1-code\\tools_service\\Test_report.docx");
RandomAccessFile raf = new RandomAccessFile(resultFile, "rw");
FileChannel fileChannel = raf.getChannel();
for (int i = 1; i <= 7; i ++) {
long position = (i - 1) * 25600;
File sliceFile = new File("D:\\1.1-code\\tools_service\\Test_report.docx_" + i);
System.out.println(sliceFile.getAbsolutePath() + "======" + sliceFile.length());
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sliceFile));
byte[] fileBytes = new byte[bis.available()];
bis.read(fileBytes);
bis.close();
fileChannel.position(position);
fileChannel.write(ByteBuffer.wrap(fileBytes));
fileChannel.force(true);
}
fileChannel.close();
raf.close();
System.out.println("文件总大小:" + resultFile.length());
} catch (Exception e) {
e.printStackTrace();
}
}
// 文件切片
public static File fileSlice(File file, long m, int totalCount, int index) {
FileInputStream in;
FileOutputStream out;
FileChannel inChannel;
FileChannel outChannel;
// long m = 51200L;
File templateFile = null;
try {
in = new FileInputStream(file);
inChannel = in.getChannel();
// 创建临时文件,并保持每个分片的文件名和原始文件名相同
String t = file.getName() + "_" + index;
templateFile = new File(t);
out = new FileOutputStream(templateFile);
outChannel = out.getChannel();
// 从inChannel的m*i处,读取固定长度的数据,写入outChannel
if (index != totalCount) {
inChannel.transferTo(m * (index - 1), m, outChannel);
} else {
// 最后一个文件,大小不固定,所以需要重新计算长度
inChannel.transferTo(m * (index-1), (file.length() - m * (index-1)), outChannel);
}
} catch (Exception e) {
e.printStackTrace();
}
return templateFile;
}
}
说两句
1、demo很简单,真正使用时,可以增加参数:文件总大小、本次切片大小,本次切片的序号等,用于判断当前是第几片文件,文件是否已完成上传,是否已完成合并;
2、尽量保持按顺序上传、合并文件;如果乱序应该会导致文件合并失败,但尚未测试,大家可以试试。
更新
网上有篇文章写了FileChannel的原理:https://www.jb51.net/article/283766.htm,有兴趣的可以看看