跳轉到

引言

在Java程式開發中,輸入輸出(IO)操作扮演著關鍵角色。無論是處理檔案、進行網路通訊,還是與使用者互動,Java的IO系統都是不可或缺的工具。

串流(Stream)為處理資料的輸入和輸出提供統一且靈活的框架。它的核心理念是將各種不同的資料來源——例如檔案、網路連接、記憶體緩衝區等——抽象化為連續的資料流,抽象化簡化資料處理的過程,使得開發者可以用一致的方式處理不同類型的資料來源。

Java IO串流概述

在Java程式設計中,串流是一個抽象的概念,代表一個連續的資料流。
這個資料流可以是從程式到外部資源(如檔案或網路連接),也可以是從外部資源到程式。
串流提供一種統一的方式來處理輸入和輸出操作,無論底層的資料來源或目的地是什麼。

串流在Java的IO系統中扮演著關鍵角色:

  1. 抽象化:串流將各種不同的輸入輸出源抽象化,使得程式設計師可以用一致的方式處理不同類型的資料。

  2. 彈性:Java提供多種不同類型的串流,可以根據需求選擇最適合的串流類型。

  3. 效能:某些串流類型(如緩衝串流)可以提高IO操作的效能。

  4. 資料轉換:串流可以進行資料的轉換,例如將位元資料轉換為字元資料。

位元串流與字元串流

在Java的IO系統中,串流主要分為兩大類:位元串流和字元串流。

位元串流

位元串流用於處理位元資料,也就是8位元組(byte)為單位的資料,適合處理二進位檔案,如圖片、音訊、視訊等。

主要的位元串流類別包括: - InputStream:所有位元輸入串流的抽象基類 - OutputStream:所有位元輸出串流的抽象基類 - FileInputStream:用於讀取檔案的位元輸入串流 - FileOutputStream:用於寫入檔案的位元輸出串流

字元串流

字元串流專門用於處理字元資料,以 16 位元的 Unicode 字元為單位,適合處理文字檔案或其他字元based的資料。

主要的字元串流類別包括: - Reader:所有字元輸入串流的抽象基類 - Writer:所有字元輸出串流的抽象基類 - FileReader:用於讀取檔案的字元輸入串流 - FileWriter:用於寫入檔案的字元輸出串流

兩者的區別和使用場景

  1. 資料單位:位元串流以位元組為單位,字元串流以字元為單位。
  2. 編碼處理:字元串流會自動處理字元編碼,而位元串流不會。
  3. 適用場景:
  4. 位元串流適合處理二進位檔案或原始資料。
  5. 字元串流適合處理文字檔案或需要字元層面操作的場景。

選擇使用哪種串流取決於您處理的資料類型。如果您在處理文字資料,使用字元串流會更方便;如果處理二進位資料或不確定資料類型,使用位元串流會更安全。

輸入串流與輸出串流

在Java的IO系統中,串流根據資料流向可以分為輸入串流和輸出串流。

輸入串流

輸入串流用於從資料源讀取資料到程式中。常見的輸入串流類別包括:

  • InputStream:位元輸入串流的抽象基類
  • Reader:字元輸入串流的抽象基類
  • FileInputStream:用於讀取檔案的位元輸入串流
  • FileReader:用於讀取檔案的字元輸入串流
  • BufferedInputStream:帶緩衝功能的位元輸入串流
  • BufferedReader:帶緩衝功能的字元輸入串流

輸出串流

輸出串流用於將資料從程式寫入到目標位置。常見的輸出串流類別包括:

  • OutputStream:位元輸出串流的抽象基類
  • Writer:字元輸出串流的抽象基類
  • FileOutputStream:用於寫入檔案的位元輸出串流
  • FileWriter:用於寫入檔案的字元輸出串流
  • BufferedOutputStream:帶緩衝功能的位元輸出串流
  • BufferedWriter:帶緩衝功能的字元輸出串流

程式碼示例:基本的檔案讀寫操作

以下是使用FileInputStream和FileOutputStream進行檔案讀寫的簡單示例:

import java.io.*;

public class FileIOExample {
    public static void main(String[] args) {
        // 檔案讀取
        try (FileInputStream fis = new FileInputStream("input.txt")) {
            int content;
            while ((content = fis.read()) != -1) {
                System.out.print((char) content);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 檔案寫入
        try (FileOutputStream fos = new FileOutputStream("output.txt")) {
            String data = "這是要寫入檔案的內容";
            byte[] byteArray = data.getBytes();
            fos.write(byteArray);
            System.out.println("資料已成功寫入檔案");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

緩衝串流

緩衝串流是Java IO系統中一個重要的概念,通過在記憶體中設置緩衝區來提高IO操作的效能。
緩衝串流可以減少實際的物理讀寫次數,從而顯著提升IO操作的速度。

緩衝串流的概念和優勢

緩衝串流在讀取或寫入資料時,會先將資料暫存在一個記憶體緩衝區中。
當緩衝區滿(在寫入時)或空(在讀取時),才會進行實際的IO操作,可以減少與底層系統的交互次數,提高效能。

主要優勢包括: 1. 減少系統呼叫次數,提高效能 2. 提供更方便的讀寫方法,如readLine() 3. 自動管理緩衝區,簡化程式設計

BufferedInputStream 和 BufferedOutputStream

這兩個類別是位元緩衝串流,用於處理位元資料。

  • BufferedInputStream:為InputStream添加緩衝功能
  • BufferedOutputStream:為OutputStream添加緩衝功能

BufferedReader 和 BufferedWriter

這兩個類別是字元緩衝串流,用於處理字元資料。

  • BufferedReader:為Reader添加緩衝功能,提供readLine()方法
  • BufferedWriter:為Writer添加緩衝功能,提供newLine()方法

程式碼示例:使用緩衝串流提升效能

以下是一個使用緩衝串流進行檔案複製的例子,展示如何使用緩衝串流來提高IO操作的效能:

import java.io.*;

public class BufferedStreamExample {
    public static void main(String[] args) {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source.txt"));
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("destination.txt"))) {

            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }
            System.out.println("檔案複製完成");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

物件串流

物件串流是Java IO系統中的一個重要組成部分,允許我們將整個Java物件寫入串流或從串流中讀取,被稱為序列化(將物件轉換為位元序列)和反序列化(將位元序列轉換回物件)。

序列化和反序列化概念

序列化是將物件的狀態轉換為可以儲存或傳輸的形式的過程。
反序列化則是將這種形式轉換回物件的過程。這兩個過程使得我們可以輕鬆地儲存物件狀態,或在網路上傳輸物件。

要使一個類別可序列化,必須實作 java.io.Serializable 介面。這是一個標記介面,不需要實作任何方法。

ObjectInputStream 和 ObjectOutputStream

Java提供兩個特殊的串流類別來處理物件的序列化和反序列化:

  • ObjectOutputStream:用於將物件寫入輸出串流(序列化)
  • ObjectInputStream:用於從輸入串流讀取物件(反序列化)

程式碼示例:物件的序列化和反序列化

以下是一個使用物件串流進行序列化和反序列化的例子:

import java.io.*;

class Person implements Serializable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

public class ObjectStreamExample {
    public static void main(String[] args) {
        Person person = new Person("張三", 30);

        // 序列化
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
            oos.writeObject(person);
            System.out.println("物件已序列化");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
            Person deserializedPerson = (Person) ois.readObject();
            System.out.println("反序列化的物件: " + deserializedPerson);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

使用物件串流時需要注意以下幾點: 1. 被序列化的類別必須實作Serializable介面 2. 類別的所有欄位也必須是可序列化的,或者被標記為transient(表示該欄位不參與序列化) 3. 序列化機制會儲存物件的完整狀態,包括私有欄位 4. 序列化版本UID(serialVersionUID)可以用來控制版本兼容性

物件串流為Java程式提供一種方便的方式來儲存和傳輸複雜的資料結構,但使用時需要注意安全性和版本控制等問題。

資料串流

資料串流是Java IO系統中的一種特殊串流,專門用於讀寫Java基本資料類型和字串,提供處理二進位格式的基本資料類型,而不需要手動進行位元組轉換。

DataInputStream 和 DataOutputStream

Java提供兩個主要的資料串流類別:

  • DataInputStream:用於從二進位串流中讀取Java基本資料類型
  • DataOutputStream:用於將Java基本資料類型寫入二進位串流

這兩個類別提供一系列方法來讀寫不同的資料類型,如readInt()、writeDouble()、readUTF()、writeBoolean()等。

程式碼示例:讀寫基本資料類型

以下是一個使用DataInputStream和DataOutputStream進行基本資料類型讀寫的例子:

import java.io.*;

public class DataStreamExample {
    public static void main(String[] args) {
        // 寫入資料
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"))) {
            dos.writeInt(100);
            dos.writeDouble(3.14159);
            dos.writeBoolean(true);
            dos.writeUTF("你好,世界!");
            System.out.println("資料已寫入檔案");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 讀取資料
        try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {
            int intValue = dis.readInt();
            double doubleValue = dis.readDouble();
            boolean booleanValue = dis.readBoolean();
            String stringValue = dis.readUTF();

            System.out.println("讀取的整數: " + intValue);
            System.out.println("讀取的浮點數: " + doubleValue);
            System.out.println("讀取的布林值: " + booleanValue);
            System.out.println("讀取的字串: " + stringValue);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用資料串流時需要注意以下幾點: 1. 讀取的順序必須與寫入的順序完全一致 2. 資料串流不會儲存資料類型資訊,所以必須確保正確的讀取順序 3. 資料串流主要用於處理原始資料類型,對於複雜的物件,應考慮使用物件串流

資料串流在處理二進位格式的資料時非常有用,特別是在需要精確控制資料格式的場景中,如網路通訊或自定義檔案格式。

印表機串流

印表機串流是Java IO系統中用於輸出格式化文字的特殊串流,提供輸出各種資料類型,並且可以自動處理格式化。

PrintStream 和 PrintWriter

Java提供兩個主要的印表機串流類別:

  • PrintStream:位元輸出串流,可以輸出各種資料類型的值
  • PrintWriter:字元輸出串流,專門用於輸出字元

這兩個類別都提供print()、println()和format()等方法,可以方便地輸出格式化的文字。

System.out 的本質

在Java中,我們經常使用System.out來輸出資訊到控制台。實際上,System.out就是一個PrintStream物件。
這就是為什麼我們可以直接使用System.out.println()來輸出各種類型的資料。

使用範例

以下是一個簡單的使用PrintStream和PrintWriter的例子:

import java.io.*;

public class PrintStreamExample {
    public static void main(String[] args) {
        // 使用 PrintStream
        try (PrintStream ps = new PrintStream(new FileOutputStream("output.txt"))) {
            ps.println("這是使用 PrintStream 輸出的文字");
            ps.printf("格式化輸出:%d, %f, %s%n", 10, 3.14, "Hello");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 使用 PrintWriter
        try (PrintWriter pw = new PrintWriter(new FileWriter("output2.txt"))) {
            pw.println("這是使用 PrintWriter 輸出的文字");
            pw.printf("格式化輸出:%d, %f, %s%n", 20, 2.718, "World");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

印表機串流的主要優點是: 1. 可以方便地輸出各種資料類型 2. 提供格式化輸出功能 3. 自動處理換行 4. 不拋出IOException,使用起來更方便

在開發過程中,當需要輸出格式化的文字或日誌時,印表機串流是一個非常有用的工具。

實踐和注意事項

在使用Java IO串流時,有一些最佳實踐和注意事項可以幫助我們更有效地進行IO操作,並避免常見的錯誤。

正確關閉串流資源

在使用完串流後,務必要正確關閉。未關閉的串流可能會導致資源洩漏,影響程式的效能和穩定性。

try-with-resources 語法

Java 7 引入的 try-with-resources 語法是一種自動資源管理的方式,可以確保串流在使用後被正確關閉。例如:

try (FileInputStream fis = new FileInputStream("input.txt");
     FileOutputStream fos = new FileOutputStream("output.txt")) {
    // 使用串流進行操作
} catch (IOException e) {
    e.printStackTrace();
}

使用這種語法,即使在發生異常的情況下,串流也會被正確關閉。

效能考量

  1. 使用緩衝串流:對於大量的IO操作,使用緩衝串流可以顯著提高效能。

  2. 適當的緩衝區大小:根據實際情況選擇合適的緩衝區大小,過大或過小都可能影響效能。

  3. 避免頻繁的小規模IO:盡量批量讀寫,減少IO操作的次數。

  4. 使用NIO:對於需要高效能的場景,考慮使用Java NIO(New IO)。

異常處理

妥善處理IO異常,不要忽視或吞掉異常。正確的異常處理可以幫助診斷和解決問題。

字元編碼

在處理文字檔案時,要注意字元編碼問題。明確指定編碼可以避免出現亂碼:

try (BufferedReader br = new BufferedReader(new InputStreamReader(
        new FileInputStream("file.txt"), "UTF-8"))) {
    // 讀取檔案內容
}

使用適當的串流類型

根據實際需求選擇合適的串流類型。例如,對於文字處理,使用字元串流;對於二進位資料,使用位元串流。

本篇文章同步刊載iThome: iThome
筆者個人的網站: JUNYI