CodeGym /課程 /JAVA 25 SELF /IOException 與 FileNotFoundException:錯誤處理

IOException 與 FileNotFoundException:錯誤處理

JAVA 25 SELF
等級 38 , 課堂 0
開放

1. 輸入/輸出例外的階層

在 Java 中,處理檔案與其他外部資源時,幾乎一定會遇到所謂的輸入/輸出例外。當讀寫資料出現問題時,會拋出(throw)對應的物件。例如,找不到檔案、沒有權限,或磁碟突然「累了」。

本講的主角

Java 對這類例外有完整的繼承階層。以下是幾個主要類別:

  • IOException — 所有 I/O 錯誤的基底類別。只要檔案、串流或網路出了問題,幾乎都與它(或其眾多子類)有關。
  • FileNotFoundExceptionIOException 的「兒子」,當你嘗試開啟不存在的檔案,或路徑有誤時就會出現。

其他衍生類別:

  • EOFException — 也是 IOException 的直接「兒子」。表示在讀取時意外抵達檔案結尾。
  • MalformedInputException — 「孫子」:繼承 CharacterCodingException,而它又繼承 IOException。當檔案無法用指定的編碼正確解讀(例如預期 "UTF-8",卻收到毀損的位元序列)時會發生。
  • 此外還有 SocketExceptionZipException 等專門的「親戚」,各自負責不同領域。越往階層深處,情境越狹窄越專一。

一個簡化的示意圖:

java.lang.Exception
└── java.io.IOException
    ├── java.io.FileNotFoundException
    ├── java.io.EOFException
    ├── java.nio.charset.MalformedInputException
    └── ... (其他)

有趣的事實:
在 Java 中,幾乎所有與檔案相關的操作都必須宣告或處理 IOException —— 這就是所謂的受檢例外。編譯器不會讓你忘記處理錯誤!

2. 這些例外何時、為何發生

開啟不存在的檔案

最常見的情況:你嘗試開啟檔案,但它不存在。就像到站牌等車,結果那班車根本不在時刻表上。

FileInputStream fis = new FileInputStream("abracadabra.txt"); // 砰!FileNotFoundException

在沒有權限的狀況下寫入檔案

如果你嘗試把檔案寫到沒有權限的資料夾,可能會得到 FileNotFoundExceptionIOException(視情況而定)。

FileOutputStream fos = new FileOutputStream("/system/secret.txt"); // 砰!FileNotFoundException 或 IOException

因為媒體損壞導致的讀寫錯誤

有時檔案看似存在,但磁碟受損、檔案被其他程式占用,或突然停電——此時你會收到帶有不同訊息的 IOException

其他原因

  • 檔案是唯讀,但你嘗試寫入。
  • 路徑太長或包含不允許的字元。
  • 檔案被其他程序使用中。
  • 磁碟空間不足。
  • 在檢查與實際使用之間,檔案被其他程序刪除。

3. 使用 try-catch 處理

每當你操作檔案、串流、網路——請使用 try-catch。這就像安全氣囊:若發生問題,程式不會直接當掉,而能妥善回應。

如何正確捕捉例外?

在 Java 中,較具體的例外要先捕捉,再來才是通用的。如果先放一個通用的 catchIOException e),較狹窄的類型(例如 FileNotFoundException)就不會觸發——控制流程到不了它們。

正確的結構:

try {
    // 檔案操作
} catch (FileNotFoundException e) {
    // 處理「檔案未找到」的情況
} catch (IOException e) {
    // 處理其他 I/O 錯誤
}

為什麼要這樣?
因為 FileNotFoundExceptionIOException 的特例。如果你先捕捉通用情況,特例就不會「走到」它自己的 catch

範例:開啟檔案時的錯誤處理

import java.io.*;

public class FileReaderExample {
    public static void main(String[] args) {
        String filename = "notes.txt";
        try {
            BufferedReader reader = new BufferedReader(new FileReader(filename));
            String line = reader.readLine();
            System.out.println("檔案的第一行: " + line);
            reader.close();
        } catch (FileNotFoundException e) {
            System.out.println("找不到檔案: " + filename);
        } catch (IOException e) {
            System.out.println("讀取檔案時發生錯誤: " + e.getMessage());
        }
    }
}

請注意:
即使你已經檢查檔案存在,也務必保留 try-catch——檔案可能隨時消失(例如被其他程序刪除)。

4. 實作:撰寫帶有錯誤處理的程式碼

我們來做幾個簡單例子,讓它們能正確應對檔案缺失與其他錯誤。

步驟 1:嘗試開啟不存在的檔案

import java.io.*;

public class NotesApp {
    public static void main(String[] args) {
        String filename = "my_notes.txt";
        try {
            BufferedReader reader = new BufferedReader(new FileReader(filename));
            String line;
            System.out.println("您的筆記:");
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            reader.close();
        } catch (FileNotFoundException e) {
            System.out.println("哎呀!找不到筆記檔案: " + filename);
            System.out.println("建議:建立檔案或檢查檔名。");
        } catch (IOException e) {
            System.out.println("讀取檔案時發生錯誤: " + e.getMessage());
        }
    }
}

步驟 2:加入寫入檔案的處理

import java.io.*;

public class NotesWriter {
    public static void main(String[] args) {
        String filename = "my_notes.txt";
        try {
            BufferedWriter writer = new BufferedWriter(new FileWriter(filename, true)); // append: true
            writer.write("新的筆記!\n");
            writer.close();
            System.out.println("成功新增筆記!");
        } catch (IOException e) {
            System.out.println("寫入檔案時發生錯誤: " + e.getMessage());
        }
    }
}

步驟 3:通用範例與錯誤紀錄

import java.io.*;

public class SafeFileCopier {
    public static void main(String[] args) {
        String source = "source.txt";
        String target = "target.txt";
        try {
            BufferedReader reader = new BufferedReader(new FileReader(source));
            BufferedWriter writer = new BufferedWriter(new FileWriter(target));
            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(line);
                writer.newLine();
            }
            reader.close();
            writer.close();
            System.out.println("檔案複製成功!");
        } catch (FileNotFoundException e) {
            System.out.println("找不到檔案: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("輸入/輸出錯誤: " + e.getMessage());
        }
    }
}

建議:
在真實應用中,錯誤不只要輸出到畫面,也應寫入檔案或日誌,以便事後追查發生了什麼問題。

5. 表格:主要的 I/O 例外

例外 何時發生? 如何處理?
FileNotFoundException
找不到檔案、路徑不存在、沒有權限 通知使用者、檢查路徑/權限,必要時建立檔案
EOFException
讀取時意外抵達檔案結尾 告知可能毀損/不完整,嘗試部分還原資料
IOException
一般 I/O 錯誤(磁碟、權限、鎖定) 檢查細節、妥善結束作業,若可行則重試
MalformedInputException
編碼或檔案結構不正確 告知可能毀損,嘗試其他編碼/來源

6. 處理 I/O 例外時的常見錯誤

錯誤 1:只捕捉通用的 Exception。 很容易想直接寫 catch (Exception e),但這樣你無法分辨究竟出了什麼問題。最好先捕捉具體的例外(FileNotFoundException),再捕捉通用的 IOException

錯誤 2:發生錯誤時未關閉串流。 如果你開了檔案,之後又拋出例外,串流可能保持未關閉。請使用 try-with-resources,或在 finally 中關閉資源。

錯誤 3:忽略例外訊息。 不要只印出「錯誤!」,請顯示詳細資訊:e.getMessage()。這能更快判斷問題所在。

錯誤 4:寫入時未處理 FileNotFoundException。 很多人以為 FileNotFoundException 只與讀取有關。其實在寫入時也可能發生(路徑不正確、沒有建立檔案的權限等)。

錯誤 5:不檢查存取權限。 如果程式以受限權限執行,許多檔案操作都可能失敗。務必考量這點並提醒使用者。

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