最近有一個需求,要將一個大文件分割爲幾個小文件,然後將小文件再合併還原成大文件。
需求很簡單,實現起來也很簡單。
- 文件分割,就是讀取大文件,然後按照指定的大小讀取到緩衝區,然後將緩衝區寫入小文件。
- 文件合併,就是按照順序讀取小文件到緩衝區,所有小文件讀取完成後,一次性將緩衝區寫入大文件
話不多說,直接看代碼:
/**
* 將大文件切割爲小文件
*
* @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。
兩種方式分割、還原文件,記錄一下。