Java中讀取一個檔案並將其拆分為多個文件
1. 概述
在本教程中,我們將學習如何用 Java 分割大檔案。首先,我們將比較讀取記憶體中的檔案和使用流讀取檔案。稍後,我們將學習根據文件的大小和數量分割文件。
2. 讀取記憶體中的文件與流中的文件
每當我們讀取記憶體中的檔案時,JVM 都會將所有行保留在記憶體中。對於小文件來說這是一個不錯的選擇。然而,對於大文件,它經常會導致OutOfMemoryException
。
串流傳輸檔案是另一種讀取檔案的方法,串流和讀取大檔案的方法有很多種。由於整個檔案不在記憶體中,因此它消耗的記憶體較少,並且可以很好地處理大檔案而不會引發異常。
對於我們的範例,我們將使用流來讀取大檔案。
3. 依檔案大小分割文件
雖然到目前為止我們已經學會了讀取大文件,但有時我們需要將它們分割成更小的文件或以更小的尺寸透過網路發送它們。
首先,我們先將大文件分割成較小的文件,每個文件都有特定的大小。
對於我們的範例,我們將在專案src/main/resource
資料夾中取得一個 4.3MB 的檔案largeFile.txt,
,並將其分割為每個檔案 1MB,並將它們儲存在/target/split
目錄下。
讓我們先獲取大檔案並在其上開啟輸入流:
File largeFile = new File("LARGE_FILE_PATH");
InputStream inputstream = Files.newInputStream(largeFile.toPath());
在這裡,我們只是載入文件元數據,大文件內容尚未載入到記憶體中。
對於我們的範例,我們有一個恆定的固定大小。在實際使用案例中,可以根據應用程式需求動態讀取和變更此maxSizeOfSplitFiles
值。
現在,我們有一個方法,它採用largeFile
物件和分割檔案定義的maxSizeOfSplitFiles
:
public List<File> splitByFileSize(File largeFile, int maxSizeOfSplitFiles<em>,</em> String splitFileDirPath)
throws IOException {
// ...
}
現在,讓我們建立一個類別SplitLargeFile
和splitByFileSize()
方法:
class SplitLargeFile {
public List<File> splitByFileSize(File largeFile, int maxSizeOfSplitFiles, String splitFileDirPath)
throws IOException {
List<File> listOfSplitFiles = new ArrayList<>();
try (InputStream in = Files.newInputStream(largeFile.toPath())) {
final byte[] buffer = new byte[maxSizeOfSplitFiles];
int dataRead = in.read(buffer);
while (dataRead > -1) {
File splitFile = getSplitFile(FilenameUtils.removeExtension(largeFile.getName()),
buffer, dataRead, splitFileDirPath);
listOfSplitFiles.add(splitFile);
dataRead = in.read(buffer);
}
}
return listOfSplitFiles;
}
private File getSplitFile(String largeFileName, byte[] buffer, int length, String splitFileDirPath)
throws IOException {
File splitFile = File.createTempFile(largeFileName + "-", "-split", new File(splitFileDirPath));
try (FileOutputStream fos = new FileOutputStream(splitFile)) {
fos.write(buffer, 0, length);
}
return splitFile;
}
}
使用maxSizeOfSplitFiles,
我們可以指定每個較小的分塊檔案可以有多少位元組。maxSizeOfSplitFiles
數量的資料將載入到記憶體中,經過處理並製成一個小檔案。然後我們擺脫它。我們讀取下一組maxSizeOfSplitFiles
資料。這可確保不會拋出OutOfMemoryException
。
最後一步,該方法傳回儲存在splitFileDirPath.
我們可以將分割檔案儲存在任何暫存目錄或任何自訂目錄中。
現在,讓我們測試一下:
public class SplitLargeFileUnitTest {
@BeforeClass
static void prepareData() throws IOException {
Files.createDirectories(Paths.get("target/split"));
}
private String splitFileDirPath() throws Exception {
return Paths.get("target").toString() + "/split";
}
private Path largeFilePath() throws Exception {
return Paths.get(this.getClass().getClassLoader().getResource("largeFile.txt").toURI());
}
@Test
void givenLargeFile_whenSplitLargeFile_thenSplitBySize() throws Exception {
File input = largeFilePath().toFile();
SplitLargeFile slf = new SplitLargeFile();
slf.splitByFileSize(input, 1024_000, splitFileDirPath());
}
}
最後,經過測試,我們可以看到程式將大文件分割成4個1MB的文件和1個240KB的文件,並將它們放在專案target/split
目錄下。
4. 依文件數分割文件
現在,讓我們將給定的大檔案拆分為指定數量的小檔案。為此,首先,我們將根據計算的文件數量檢查小文件的大小是否適合。
此外,我們將在內部使用與先前相同的splitByFileSize()
方法來進行實際的分割.
讓我們建立一個方法splitByNumberOfFiles()
:
class SplitLargeFile {
public List<File> splitByNumberOfFiles(File largeFile, int noOfFiles, String splitFileDirPath)
throws IOException {
return splitByFileSize(largeFile, getSizeInBytes(largeFile.length(), noOfFiles), splitFileDirPath);
}
private int getSizeInBytes(long largefileSizeInBytes, int numberOfFilesforSplit) {
if (largefileSizeInBytes % numberOfFilesforSplit != 0) {
largefileSizeInBytes = ((largefileSizeInBytes / numberOfFilesforSplit) + 1) * numberOfFilesforSplit;
}
long x = largefileSizeInBytes / numberOfFilesforSplit;
if (x > Integer.MAX_VALUE) {
throw new NumberFormatException("size too large");
}
return (int) x;
}
}
現在,讓我們測試一下:
@Test
void givenLargeFile_whenSplitLargeFile_thenSplitByNumberOfFiles() throws Exception {
File input = largeFilePath().toFile();
SplitLargeFile slf = new SplitLargeFile();
slf.splitByNumberOfFiles(input, 3, splitFileDirPath());
}
最後,經過測試,我們可以看到程式將大文件分割成3個1.4MB的文件,並將其放在專案target/split
目錄下。
5. 結論
在本文中,我們了解了在記憶體中讀取檔案和透過流讀取檔案之間的差異,這有助於我們為任何用例選擇合適的檔案。後來我們討論瞭如何將大文件拆分成小文件。然後我們了解了按大小拆分和按文件數量拆分。
與往常一樣,本文中使用的範例程式碼位於 GitHub 上。