Java實現大文件分割與合併

最近有一個需求,要將一個大文件分割爲幾個小文件,然後將小文件再合併還原成大文件。

需求很簡單,實現起來也很簡單。

  • 文件分割,就是讀取大文件,然後按照指定的大小讀取到緩衝區,然後將緩衝區寫入小文件。
  • 文件合併,就是按照順序讀取小文件到緩衝區,所有小文件讀取完成後,一次性將緩衝區寫入大文件

話不多說,直接看代碼:

/**
 * 將大文件切割爲小文件
 *
 * @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。

兩種方式分割、還原文件,記錄一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章