最近有一个需求,要将一个大文件分割为几个小文件,然后将小文件再合并还原成大文件。
需求很简单,实现起来也很简单。
- 文件分割,就是读取大文件,然后按照指定的大小读取到缓冲区,然后将缓冲区写入小文件。
- 文件合并,就是按照顺序读取小文件到缓冲区,所有小文件读取完成后,一次性将缓冲区写入大文件
话不多说,直接看代码:
/**
* 将大文件切割为小文件
*
* @param inputFile 大文件
* @param tmpPath 小文件临时目录
* @param bufferSize 切割小文件大小
*/
public static void splitFile(String inputFile, String tmpPath, Integer bufferSize) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 原始大文件
fis = new FileInputStream(inputFile);
// 文件读取缓存
byte[] buffer = new byte[bufferSize];
int len = 0;
// 切割后的文件计数(也是文件名)
int fileNum = 0;
// 大文件切割成小文件
while ((len = fis.read(buffer)) != -1) {
fos = new FileOutputStream(tmpPath + "/" + fileNum);
fos.write(buffer, 0, len);
fos.close();
fileNum++;
}
System.out.println("分割文件" + inputFile + "完成,共生成" + fileNum + "个文件");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 合并切割小文件为大文件
*
* @param tmpPath 小文件临时目录
* @param outputPath 输出路径
* @param bufferSize 切割小文件大小
*/
public static void mergeFile(String tmpPath, String outputPath, Integer bufferSize) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 获取切割的小文件数目
File tempFilePath = new File(tmpPath);
File[] files = tempFilePath.listFiles();
if (files == null) {
System.out.println("No file.");
return;
}
int fileNum = files.length;
// 还原的大文件路径
String outputFile = outputPath + "/" + generateFileName();
fos = new FileOutputStream(outputFile);
// 文件读取缓存
byte[] buffer = new byte[bufferSize];
int len = 0;
// 还原所有切割小文件到一个大文件中
for (int i = 0; i < fileNum; i++) {
fis = new FileInputStream(tmpPath + "/" + i);
len = fis.read(buffer);
fos.write(buffer, 0, len);
}
System.out.println("合并目录文件:" + tmpPath + "完成,生成文件为:" + outputFile);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 生成随机文件名
*
* @return 文件名
*/
public static String generateFileName() {
String time = DateFormatUtils.format(new Date(), "yyyMMddHHmmss");
return time + ".7z";
}
测试上述代码
String localInputFile = "F:/test/file/in/in.7z";
String localTmpPath = "F:/test/file/tmp";
String localOutputPath = "F:/test/file/out";
Integer localBufferSize = 1024 * 1024;
splitFile(localInputFile, localTmpPath, localBufferSize);
mergeFile(localTmpPath, localOutputPath, localBufferSize);
以上就是切割文件与合并文件的代码,逻辑很简单,是使用二进制方式读取的,所以打开的临时文件会是乱码。
如果要读取成字符串,就可以避免乱码的问题了。
那么如何读取成字符串呢?
我们可以将读取成的二进制流,再通过Base64编码,这样就可以变成字符串了。但是与此同时也带来一个问题,就是小文件大小会略有膨胀。
以下是实现代码:
/**
* 将大文件切割为小文件(字符串)
*
* @param inputFile 要切割的大文件
* @param tmpPath 小文件临时目录
* @param bufferSize 切割大小(二进制读取大小)
*/
public static void splitFileByChar(String inputFile, String tmpPath, Integer bufferSize) {
FileInputStream fis = null;
FileWriter fw = null;
try {
// 原始大文件
fis = new FileInputStream(inputFile);
// 文件读取缓存
byte[] buffer = new byte[bufferSize];
// 切割后的文件计数(也是文件名)
int fileNum = 0;
// 大文件切割成小文件
while ((fis.read(buffer)) != -1) {
fw = new FileWriter(tmpPath + "/" + fileNum + ".txt");
// base64将二进制流转为字符串
String tmpStr = Base64.getEncoder().encodeToString(buffer);
fw.write(tmpStr);
fw.close();
fileNum++;
}
System.out.println("分割文件" + inputFile + "完成,共生成" + fileNum + "个文件");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 合并小文件为大文件
*
* @param tmpPath 小文件临时目录
* @param outputPath 大文件输出目录
* @param bufferSize 切割大小(二进制读取大小)
*/
public static void mergeFileByChar(String tmpPath, String outputPath, Integer bufferSize) {
FileReader fr = null;
FileOutputStream fos = null;
try {
// 获取切割的小文件数目
File tempFilePath = new File(tmpPath);
File[] files = tempFilePath.listFiles();
if (files == null || files.length <= 0) {
System.out.println("No file.");
return;
}
int fileNum = files.length;
// 生成的大文件路径
String outputFile = outputPath + "/" + generateFileName();
fos = new FileOutputStream(outputFile);
// 还原所有切割小文件到一个大文件中
for (int i = 0; i < fileNum; i++) {
fr = new FileReader(tmpPath + "/" + i + ".txt");
// 读取出base64编码后的数据(*2减少读取次数,因为base64之后文件会略有膨胀)
char[] buffer = new char[bufferSize * 2];
int len;
StringBuilder tmpStr = new StringBuilder();
while ((len = fr.read(buffer)) != -1) {
tmpStr.append(new String(buffer, 0, len));
}
// base64将字符转为二进制流
byte[] tmpBuffer = Base64.getDecoder().decode(tmpStr.toString());
fos.write(tmpBuffer, 0, tmpBuffer.length);
}
System.out.println("合并目录文件:" + tmpPath + "完成,生成文件为:" + outputFile);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 生成随机文件名
*
* @return 文件名
*/
public static String generateFileName() {
String time = DateFormatUtils.format(new Date(), "yyyMMddHHmmss");
return time + ".7z";
}
测试上述代码
String localInputFile = "F:/test/file/in/in.7z";
String localTmpPath = "F:/test/file/tmp";
String localOutputPath = "F:/test/file/out";
Integer localBufferSize = 1024 * 1024;
splitFileByChar(localInputFile, localTmpPath, localBufferSize);
mergeFileByChar(localTmpPath, localOutputPath, localBufferSize);
打开临时目录,就能看到我们想要的字符串格式小文件了。不过每个小文件大小都超过了我们预设的1M大小,约为1366K。
两种方式分割、还原文件,记录一下。