首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java IO API 深度解析:流、節點與處理流全面指南

     編輯:Java 程式設計入門

一、核心理論基礎

在 Java 程式設計中,「流」是一個抽象概念,它封裝了輸入/輸出設備的底層操作細節。無論是檔案、網路連線還是記憶體,資料的傳輸都以「流」的形式進行。流具有方向性:以程式(或 JVM)為參考,資料從程式流向設備稱為輸出流,反之則為輸入流。這就像水管一樣,方向決定了資料的流向。

流的分類體系

  • 按資料單位劃分:
    InputStream/OutputStream 及其子類(以 Stream 結尾)處理位元組流(byte,8 bits),適用於二進位檔案(圖片、執行檔、壓縮包等);
    Reader/Writer 及其子類處理字元流(char,16 bits),適用於文字檔案(文字、XML、JSON 等)。
  • 按角色劃分:
    節點流:直接連接資料源(檔案、記憶體、管道),如 FileInputStream、ByteArrayInputStream。
    處理流:包裝節點流,提供緩衝、轉換、序列化等增強功能,如 BufferedInputStream、InputStreamReader、ObjectInputStream。

位元組流繼承體系圖
字元流繼承體系圖

關鍵設計原則

  • 所有輸入流繼承自 InputStream 或 Reader,提供 read() 方法;輸出流繼承自 OutputStream 或 Writer,提供 write() 方法。
  • 字元流最終必須轉換為位元組流才能輸出(透過 OutputStreamWriter/InputStreamReader)。
  • Java 2 SDK 提供三種基本節點:檔案、記憶體、管道。

二、實用選型指南

根據使用場景快速選擇適當的流類別:

分類依據場景推薦類別
資料來源/去向檔案FileInputStream, FileOutputStream, FileReader, FileWriter
位元組陣列ByteArrayInputStream, ByteArrayOutputStream
字元陣列CharArrayReader, CharArrayWriter
字串StringReader, StringWriter
是否格式化輸出需要PrintStream, PrintWriter
不需要一般流類別
是否需要緩衝需要BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter
不需要一般流類別
資料格式二進位/不確定InputStream, OutputStream 及其子類
純文字Reader, Writer 及其子類
特殊需求位元組↔字元轉換InputStreamReader, OutputStreamWriter
物件序列化ObjectInputStream, ObjectOutputStream
行號追蹤LineNumberReader, LineNumberInputStream
推回輸入PushbackReader, PushbackInputStream

選型決策樹(簡化版)

  1. 確認資料是文字還是二進位?
  2. 確認是輸入還是輸出?
  3. 資料來源是檔案、記憶體還是網路?
  4. 是否需要緩衝提升效能?(特別注意 readLine() 等特有方法)
  5. 是否需要格式化輸出?
  6. 是否需要轉換流(位元組↔字元)?

三、經典程式碼範例解析

1. 標準輸入處理(System.in)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class TestSystemIn {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
            String line;
            while ((line = br.readLine()) != null) {
                if ("exit".equalsIgnoreCase(line)) break;
                System.out.println(line.toUpperCase());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

重點:System.in 是位元組流,透過 InputStreamReader 轉為字元流,再用 BufferedReader 獲得 readLine() 便利方法。使用 try-with-resources 自動關閉資源。

2. 緩衝字元流複製檔案

import java.io.*;

public class TestBuffer {
    public static void main(String[] args) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("d:/java.txt"));
             BufferedReader br = new BufferedReader(new FileReader("d:/java.txt"))) {
            
            for (int i = 1; i < 100; i++) {
                bw.write(String.valueOf(Math.random()));
                bw.newLine(); // 跨平台換行
            }
            bw.flush(); // 強制刷新緩衝區
            
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

重點:BufferedWriter 提供 newLine() 和 flush();BufferedReader 提供 readLine()。緩衝顯著提升大量資料讀寫效能。

3. 位元組流讀取檔案(含中文亂碼示範)

import java.io.*;

public class TestFileInputStream {
    public static void main(String[] args) {
        try (FileInputStream in = new FileInputStream("e:/1.txt")) {
            int b;
            long count = 0;
            while ((b = in.read()) != -1) {
                System.out.print((char) b); // 直接轉 char 會導致中文亂碼
                count++;
            }
            System.out.println("\n共讀取 " + count + " 個位元組");
        } catch (FileNotFoundException e) {
            System.err.println("找不到檔案");
        } catch (IOException e) {
            System.err.println("讀取錯誤");
        }
    }
}

教訓:位元組流讀取中文會產生亂碼,因為一個中文字元佔 2-3 個位元組,需使用字元流(FileReader)或指定編碼的轉換流。

4. 位元組流實現檔案複製

import java.io.*;

public class TestFileOutputStream {
    public static void main(String[] args) {
        try (FileInputStream in = new FileInputStream("d:/java.txt");
             FileOutputStream out = new FileOutputStream("d:/my_java.txt")) {
            
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            System.out.println("檔案已複製");
        } catch (IOException e) {
            System.err.println("複製失敗: " + e.getMessage());
        }
    }
}

改進建議:實際專案中應使用緩衝流(BufferedInputStream/BufferedOutputStream)或 NIO Files.copy() 以獲得更好效能。

5. 物件序列化與 transient 關鍵字

import java.io.*;

class T implements Serializable {
    int i = 10;
    int j = 9;
    double d = 2.3;
    transient int k = 15; // 序列化時忽略此欄位
}

public class TestObjectIO {
    public static void main(String[] args) throws Exception {
        T obj = new T();
        obj.k = 8; // 修改後的值不會被序列化
        
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:/1.txt"))) {
            oos.writeObject(obj);
        }
        
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/1.txt"))) {
            T restored = (T) ois.readObject();
            System.out.println(restored.i + " " + restored.j + " " + restored.k); // k 輸出 0 (預設值)
        }
    }
}

重點:實作 Serializable 介面啟用序列化;transient 標記的欄位不參與序列化,反序列化時為預設值(int 為 0)。

6. 字元編碼轉換實戰

import java.io.*;

public class TestTransForm {
    public static void main(String[] args) {
        try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d:/java.txt"), "UTF-8")) {
            osw.write("你好123");
            System.out.println("編碼方式:" + osw.getEncoding()); // 輸出 UTF-8
            
            // 追加模式,指定 ISO-8859-1
            try (OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("d:/java.txt", true), "ISO-8859-1")) {
                osw2.write("這是追加的內容");
                System.out.println("編碼方式:" + osw2.getEncoding());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

關鍵:OutputStreamWriter 是位元組流到字元流的橋樑,務必顯式指定編碼,避免平台預設編碼導致亂碼。中文 Windows 預設 GBK,Linux/Mac 預設 UTF-8。

7. 輸出重定向與 Print 流

import java.io.*;

public class TestPrintStream {
    public static void main(String[] args) {
        try (PrintStream ps = new PrintStream(new FileOutputStream("d:/java.txt"))) {
            System.setOut(ps); // 將標準輸出重定向到檔案
            
            int lineCount = 0;
            for (char c = 0; c < 65535; c++) {
                System.out.print(c + " ");
                if (++lineCount > 100) {
                    System.out.println();
                    lineCount = 0;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

特性:PrintStream/PrintWriter 不拋出受檢異常,提供 println()、printf() 等格式化方法,適合日誌輸出。

8. 基本型別資料流

import java.io.*;

public class TestDataStream {
    public static void main(String[] args) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             DataOutputStream dos = new DataOutputStream(baos);
             ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
             DataInputStream dis = new DataInputStream(bais)) {
            
            dos.writeDouble(Math.random());
            dos.writeBoolean(true);
            
            System.out.println("可用位元組: " + bais.available());
            System.out.println("讀取 double: " + dis.readDouble());
            System.out.println("讀取 boolean: " + dis.readBoolean());
            // 注意:讀取順序必須與寫入順序一致
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

用途:DataInputStream/DataOutputStream 以機器無關方式讀寫 Java 基本型別,適合網路傳輸或二進位檔案儲存結構化資料。

四、常見疑問解析

Q: 為什麼 Reader/Writer 不繼承 InputStream/OutputStream?
A: 這是單一職責原則的體現。位元組流處理原始二進位資料,字元流處理編碼後的文字。強行繼承會導致設計混亂:字元流內部必須持有位元組流並處理編碼轉換,透過組合(InputStreamReader 包裝 InputStream)比繼承更靈活、解耦。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved