1. 介紹
先從最基本開始:二進位檔和文字檔有什麼不同?文字檔是可以用一般記事本打開,看到字母、數字、空格與其他符號的檔案。例如,my_notes.txt 或 poem.txt。
二進位檔案不是文字,而是任意位元組。可能是圖片(.jpg、.png)、音樂(.mp3)、壓縮檔(.zip)、可執行檔(.exe)、影片(.mp4)、資料庫檔等等。如果用記事本開啟這類檔案,你會看到像 ÿØÿà 之類的內容,或是一整片看不懂的符號。這很正常!電腦只「理解」位元組——對它來說,文字、圖片、影片都只是位元組序列。文字檔中的位元組可以被詮釋為字元,而在二進位檔中,它們是「原始」資料,並非供人直接閱讀。
處理二進位檔案的主要類別
在 Java 中,處理二進位檔案使用位元組串流:
- InputStream — 讀取位元組的基底類別。
- OutputStream — 寫入位元組的基底類別。
對於檔案,有其具體實作:
- FileInputStream — 從檔案讀取位元組。
- FileOutputStream — 將位元組寫入檔案。
如果你聽過 FileReader 和 FileWriter,請注意:它們處理的是字元,只適用於文字。對於二進位檔案請只使用 InputStream/OutputStream 及其子類。
2. 讀取二進位檔案
逐一位元組讀取
最簡單的方法是一次讀取一個位元組。這很直觀,但非常慢。
try (FileInputStream in = new FileInputStream("image.jpg")) {
int b;
while ((b = in.read()) != -1) {
// b 是 0 到 255 的數字(位元組),-1 表示檔案結尾
// 可以處理該位元組,例如計算所有位元組的總和
}
}
方法 read() 會以 int 回傳下一個位元組(從 0 到 255),而當檔案結束時則回傳 -1。通常只有在需要非常特殊的處理時(例如分析檔案結構)才會逐一位元組讀取。
以區塊(含緩衝區)讀取
一次讀一個位元組就像為了每顆蘋果都跑一趟商店。更有效率的是一次拿一整袋!在 Java 中可以使用 read(byte[] buffer) 方法,將位元組讀入陣列。
try (FileInputStream in = new FileInputStream("image.jpg")) {
byte[] buffer = new byte[4096]; // 4 KB 的緩衝區
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
// buffer 中包含從檔案讀到的 bytesRead 個位元組
// 可以處理這些位元組,例如將它們存到別處
}
}
方法 read(buffer) 會回傳實際讀到的位元組數(可能小於緩衝區大小,特別是在最後一次讀取)。這種方式快得多,因為對磁碟的存取次數更少。
範例:複製檔案
我們來寫一個簡單的程式,將任意二進位檔(例如圖片)從一個位置複製到另一個位置。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BinaryCopyExample {
public static void main(String[] args) {
String source = "cat.jpg";
String dest = "cat_copy.jpg";
try (FileInputStream in = new FileInputStream(source);
FileOutputStream out = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192]; // 8 KB — 對多數情況而言是最佳大小
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
System.out.println("複製完成!");
} catch (IOException e) {
System.out.println("複製時發生錯誤:" + e.getMessage());
}
}
}
很簡單:從來源檔按區塊讀取,立刻寫入新檔。這種方式適用於任何檔案:圖片、壓縮檔、影片。
3. 寫入二進位檔案
寫入位元組陣列
如果你已有一個位元組陣列(例如從網路取得或在程式中產生),你可以這樣寫入檔案:
byte[] data = new byte[] {1, 2, 3, 4, 5}; // 範例陣列
try (FileOutputStream out = new FileOutputStream("data.bin")) {
out.write(data); // 將整個陣列寫入檔案
}
方法 write(byte[]) 會寫入陣列中的所有位元組。也可以只寫入部分陣列:out.write(data, offset, length)。
分段寫入檔案(例如做複製時)
和讀取一樣,通常會使用緩衝區:
try (FileInputStream in = new FileInputStream("source.bin");
FileOutputStream out = new FileOutputStream("dest.bin")) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
在這裡,我們從檔案讀到什麼就立刻寫到另一個檔案。這類程式碼常見於壓縮工具、下載器、影像處理器等。
4. 實用細節
為什麼不能在二進位檔案上用 Reader/Writer?
Reader 和 Writer 處理的是字元(char),而不是位元組。它們會依照編碼(例如 UTF-8)自動把位元組轉成字元。這對文字很方便,但對二進位檔案——致命危險!
如果你嘗試用 FileWriter 寫入圖片,會得到損毀且無法開啟的檔案。請記住:對任何非文字檔案,一律只使用 InputStream/OutputStream!
處理二進位檔案的重要差異與細節
- 緩衝區大小:太小會變慢(對磁碟的存取太多),太大會佔用多餘記憶體。通常 4–16 KB 最為合適。
- 錯誤處理:務必處理 IOException——檔案可能不存在、被鎖定,或磁碟空間已用盡。
- 關閉串流:使用 try-with-resources——即使發生錯誤也能確保檔案被關閉。
- 覆寫檔案:如果你透過 new FileOutputStream("file.bin") 開啟檔案,它會被覆寫。若要在結尾追加,使用帶參數 append = true 的建構子。
- 存取權限:若程式無法開啟檔案,請檢查讀寫權限。
- readAllBytes():可以一次把整個檔案讀成位元組陣列。對大檔案——不要使用,以免「吃」光所有記憶體!
5. 常見錯誤
錯誤 1:在二進位檔案上使用 FileReader/FileWriter。 這會破壞資料,因為這些類別會在位元組與字元之間轉換;對圖片、壓縮檔等來說是災難。
錯誤 2:忽略 read() 的回傳值。 方法 read(byte[]) 可能讀到比你要求更少的位元組,特別是在最後一個區塊。一定要使用其回傳值,才能知道實際處理了多少位元組。
錯誤 3:忘記關閉串流。 如果不關閉檔案,它可能保持被鎖定,而資料也可能無法完全寫入(特別是在寫入時!)。請使用 try-with-resources。
錯誤 4:不顧大小,一次把整個檔案讀進記憶體。 對大檔案會導致 OutOfMemoryError。請使用緩衝區並分段讀取。
錯誤 5:未處理例外。 檔案操作隨時可能出錯:找不到檔案、沒有權限、磁碟已滿。別忘了處理 IOException。
GO TO FULL VERSION