CodeGym /課程 /JAVA 25 SELF /二進位檔案的讀取與寫入:InputStream、OutputStream

二進位檔案的讀取與寫入:InputStream、OutputStream

JAVA 25 SELF
等級 36 , 課堂 2
開放

1. 介紹

先從最基本開始:二進位檔和文字檔有什麼不同?文字檔是可以用一般記事本打開,看到字母、數字、空格與其他符號的檔案。例如,my_notes.txtpoem.txt

二進位檔案不是文字,而是任意位元組。可能是圖片(.jpg.png)、音樂(.mp3)、壓縮檔(.zip)、可執行檔(.exe)、影片(.mp4)、資料庫檔等等。如果用記事本開啟這類檔案,你會看到像 ÿØÿà 之類的內容,或是一整片看不懂的符號。這很正常!電腦只「理解」位元組——對它來說,文字、圖片、影片都只是位元組序列。文字檔中的位元組可以被詮釋為字元,而在二進位檔中,它們是「原始」資料,並非供人直接閱讀。

處理二進位檔案的主要類別

在 Java 中,處理二進位檔案使用位元組串流:

  • InputStream — 讀取位元組的基底類別。
  • OutputStream — 寫入位元組的基底類別。

對於檔案,有其具體實作:

  • FileInputStream — 從檔案讀取位元組。
  • FileOutputStream — 將位元組寫入檔案。

如果你聽過 FileReaderFileWriter,請注意:它們處理的是字元,只適用於文字。對於二進位檔案請只使用 InputStream/OutputStream 及其子類

2. 讀取二進位檔案

逐一位元組讀取

最簡單的方法是一次讀取一個位元組。這很直觀,但非常慢。

try (FileInputStream in = new FileInputStream("image.jpg")) {
    int b;
    while ((b = in.read()) != -1) {
        // b 是 0 到 255 的數字(位元組),-1 表示檔案結尾
        // 可以處理該位元組,例如計算所有位元組的總和
    }
}

方法 read() 會以 int 回傳下一個位元組(從 0255),而當檔案結束時則回傳 -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?

ReaderWriter 處理的是字元(char),而不是位元組。它們會依照編碼(例如 UTF-8)自動把位元組轉成字元。這對文字很方便,但對二進位檔案——致命危險!

如果你嘗試用 FileWriter 寫入圖片,會得到損毀且無法開啟的檔案。請記住:對任何非文字檔案,一律只使用 InputStream/OutputStream

處理二進位檔案的重要差異與細節

  • 緩衝區大小:太小會變慢(對磁碟的存取太多),太大會佔用多餘記憶體。通常 416 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

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION