IO流概念與分類

在Java編程中,我們經常需要讀取或寫入文件、網絡數據等,這些操作都離不開IO流。IO(Input/Output)流是Java中用於處理輸入輸出的核心機制,它可以將數據從一個地方傳輸到另一個地方,比如從文件到程序,或者從程序到網絡。

IO流的體系結構

Java的IO流體系非常龐大,但整體上可以分為兩大派系:字節流和字符流。字節流以字節為單位處理數據,適用於所有類型的文件;字符流以字符為單位處理數據,專門用於文本文件。下面是IO流的體系結構圖:

Java21天學習計劃 - 第九天:IO流與文件操作基礎_字符流

從圖中可以看出,IO流主要分為以下幾類:

  1. 按數據流向分
  • 輸入流(InputStream/Reader):從數據源讀取數據到程序
  • 輸出流(OutputStream/Writer):從程序寫入數據到目的地
  1. 按數據單位分
  • 字節流(InputStream/OutputStream):以字節為單位(8位)
  • 字符流(Reader/Writer):以字符為單位(16位Unicode)
  1. 按流的角色分
  • 節點流(低級流):直接與數據源相連,如FileInputStream
  • 處理流(高級流/裝飾流):包裝節點流,增強功能,如BufferedInputStream

字節流與字符流對比

字節流和字符流是IO流體系中最核心的兩種流,它們的主要區別如下:

Java21天學習計劃 - 第九天:IO流與文件操作基礎_System_02

表格

複製

類型

輸入流

輸出流

特點

字節流

InputStream

OutputStream

以字節為單位,適用於所有文件

字符流

Reader

Writer

以字符為單位,適用於文本文件,會進行編碼轉換

使用場景

  • 處理文本文件(如.txt、.java):優先使用字符流,可避免中文亂碼
  • 處理非文本文件(如圖片、音頻、視頻):必須使用字節流
  • 網絡數據傳輸:通常使用字節流

文件操作

在Java中,File類是文件和目錄路徑名的抽象表示,它提供了一系列方法來操作文件和目錄。通過File類,我們可以創建、刪除、重命名文件,查詢文件屬性等。

File類常用方法

File類的常用方法如下表所示:

表格

複製

方法

功能描述

boolean exists()

判斷文件或目錄是否存在

boolean createNewFile()

創建新文件

boolean mkdir()

創建單級目錄

boolean mkdirs()

創建多級目錄

boolean delete()

刪除文件或空目錄

boolean renameTo(File dest)

重命名文件或目錄

String getName()

獲取文件或目錄名

String getPath()

獲取路徑

String getAbsolutePath()

獲取絕對路徑

long length()

獲取文件長度(字節數)

boolean isFile()

判斷是否為文件

boolean isDirectory()

判斷是否為目錄

String[] list()

獲取目錄下的所有文件和目錄名

File[] listFiles()

獲取目錄下的所有文件和目錄對象

文件操作案例

下面通過一個案例來演示File類的常用操作:

複製

import java.io.File;
import java.io.IOException;

public class FileOperationDemo {
    public static void main(String[] args) {
        // 創建File對象
        File file = new File("test.txt");

        try {
            // 判斷文件是否存在
            if (!file.exists()) {
                // 創建新文件
                boolean created = file.createNewFile();
                if (created) {
                    System.out.println("文件創建成功!");
                } else {
                    System.out.println("文件創建失敗!");
                }
            }

            // 獲取文件信息
            System.out.println("文件名:" + file.getName());
            System.out.println("文件路徑:" + file.getPath());
            System.out.println("絕對路徑:" + file.getAbsolutePath());
            System.out.println("文件大小:" + file.length() + "字節");
            System.out.println("是否為文件:" + file.isFile());

            // 創建目錄
            File dir = new File("testDir");
            if (!dir.exists()) {
                boolean mkdir = dir.mkdir();
                if (mkdir) {
                    System.out.println("目錄創建成功!");
                }
            }

            // 重命名文件
            File newFile = new File("newTest.txt");
            boolean renamed = file.renameTo(newFile);
            if (renamed) {
                System.out.println("文件重命名成功!");
            }

            // 刪除文件
            boolean deleted = newFile.delete();
            if (deleted) {
                System.out.println("文件刪除成功!");
            }

            // 刪除目錄
            boolean dirDeleted = dir.delete();
            if (dirDeleted) {
                System.out.println("目錄刪除成功!");
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

運行結果

複製

文件創建成功!
文件名:test.txt
文件路徑:test.txt
絕對路徑:/Users/username/JavaProject/test.txt
文件大小:0字節
是否為文件:true
目錄創建成功!
文件重命名成功!
文件刪除成功!
目錄刪除成功!

注意事項

  • delete()方法刪除目錄時,目錄必須為空
  • createNewFile()方法只能創建文件,不能創建目錄
  • mkdir()只能創建單級目錄,mkdirs()可以創建多級目錄

字節流

字節流是Java IO中最基礎的流,它以字節為單位處理數據。字節流主要包括InputStream和OutputStream兩個抽象類,以及它們的一系列子類。

FileInputStream和FileOutputStream

FileInputStream和FileOutputStream是字節流中最常用的類,它們分別用於從文件讀取字節和向文件寫入字節。

構造方法

複製

// FileInputStream構造方法
FileInputStream(String name)
FileInputStream(File file)

// FileOutputStream構造方法
FileOutputStream(String name)
FileOutputStream(File file)
FileOutputStream(String name, boolean append) // append為true時追加寫入

常用方法

複製

// 讀取一個字節,返回值為字節的ASCII碼,-1表示文件結束
int read()

// 讀取多個字節到字節數組,返回實際讀取的字節數,-1表示文件結束
int read(byte[] b)

// 寫入一個字節
void write(int b)

// 寫入字節數組
void write(byte[] b)

// 寫入字節數組的一部分
void write(byte[] b, int off, int len)

// 關閉流
void close()

BufferedInputStream和BufferedOutputStream

BufferedInputStream和BufferedOutputStream是帶緩衝的字節流,它們通過內部緩衝區來提高讀寫效率。使用緩衝流可以減少IO操作次數,從而提高性能。

構造方法

複製

BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size) // 指定緩衝區大小

BufferedOutputStream(OutputStream out)
BufferedOutputStream(OutputStream out, int size) // 指定緩衝區大小

圖片複製案例

下面通過一個案例來演示如何使用字節流複製圖片:

複製

import java.io.*;

public class ImageCopyDemo {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();

        // 源文件和目標文件路徑
        String srcPath = "source.jpg";
        String destPath = "dest.jpg";

        // 聲明流對象
        InputStream in = null;
        OutputStream out = null;

        try {
            // 創建帶緩衝的字節流
            in = new BufferedInputStream(new FileInputStream(srcPath));
            out = new BufferedOutputStream(new FileOutputStream(destPath));

            // 讀取數據
            byte[] buffer = new byte[1024]; // 1KB緩衝區
            int len;

            // 循環讀取數據
            while ((len = in.read(buffer)) != -1) {
                // 寫入數據
                out.write(buffer, 0, len);
            }

            // 刷新緩衝區
            out.flush();

            long endTime = System.currentTimeMillis();
            System.out.println("圖片複製成功!耗時:" + (endTime - startTime) + "毫秒");

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 關閉流
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

運行結果

複製

圖片複製成功!耗時:23毫秒

流程圖

文件複製的流程如下所示:

Java21天學習計劃 - 第九天:IO流與文件操作基礎_System_03

注意事項

  • 使用緩衝流可以顯著提高讀寫效率,推薦優先使用
  • 流操作完成後必須關閉,通常在finally塊中關閉
  • 寫入數據後,最好調用flush()方法刷新緩衝區
  • 讀取數據時,使用byte數組作為緩衝區可以提高效率

字符流

字符流以字符為單位處理數據,它會自動進行字符編碼和解碼,因此特別適合處理文本文件。字符流主要包括Reader和Writer兩個抽象類,以及它們的一系列子類。

FileReader和FileWriter

FileReader和FileWriter是字符流中最常用的類,它們分別用於從文件讀取字符和向文件寫入字符。

構造方法

複製

// FileReader構造方法
FileReader(String name)
FileReader(File file)

// FileWriter構造方法
FileWriter(String name)
FileWriter(File file)
FileWriter(String name, boolean append) // append為true時追加寫入

常用方法

複製

// 讀取一個字符,返回值為字符的Unicode碼,-1表示文件結束
int read()

// 讀取多個字符到字符數組,返回實際讀取的字符數,-1表示文件結束
int read(char[] cbuf)

// 寫入一個字符
void write(int c)

// 寫入字符數組
void write(char[] cbuf)

// 寫入字符數組的一部分
void write(char[] cbuf, int off, int len)

// 寫入字符串
void write(String str)

// 寫入字符串的一部分
void write(String str, int off, int len)

// 關閉流
void close()

BufferedReader和BufferedWriter

BufferedReader和BufferedWriter是帶緩衝的字符流,它們通過內部緩衝區來提高讀寫效率。此外,BufferedReader還提供了readLine()方法,可以方便地讀取一行文本。

構造方法

複製

BufferedReader(Reader in)
BufferedReader(Reader in, int size) // 指定緩衝區大小

BufferedWriter(Writer out)
BufferedWriter(Writer out, int size) // 指定緩衝區大小

BufferedReader特有方法

複製

// 讀取一行文本,返回值為讀取的字符串,null表示文件結束
String readLine()

BufferedWriter特有方法

複製

// 寫入一個行分隔符
void newLine()

文本文件讀寫案例

下面通過一個案例來演示如何使用字符流讀寫文本文件:

複製

import java.io.*;

public class TextFileDemo {
    public static void main(String[] args) {
        // 寫入文本文件
        writeTextFile("test.txt", "Hello World!\nJava IO流真有趣!");

        // 讀取文本文件
        String content = readTextFile("test.txt");
        System.out.println("文件內容:");
        System.out.println(content);
    }

    // 寫入文本文件
    public static void writeTextFile(String fileName, String content) {
        BufferedWriter writer = null;
        try {
            // 創建帶緩衝的字符輸出流
            writer = new BufferedWriter(new FileWriter(fileName));

            // 寫入內容
            writer.write(content);

            System.out.println("文件寫入成功!");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 關閉流
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 讀取文本文件
    public static String readTextFile(String fileName) {
        BufferedReader reader = null;
        StringBuilder sb = new StringBuilder();

        try {
            // 創建帶緩衝的字符輸入流
            reader = new BufferedReader(new FileReader(fileName));

            String line;
            // 讀取一行文本
            while ((line = reader.readLine()) != null) {
                sb.append(line).append("\n");
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 關閉流
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return sb.toString();
    }
}

運行結果

複製

文件寫入成功!
文件內容:
Hello World!
Java IO流真有趣!

注意事項

  • 使用BufferedReader的readLine()方法可以方便地按行讀取文本
  • newLine()方法會根據操作系統自動使用相應的行分隔符
  • 字符流會使用默認編碼,可能導致中文亂碼。若要指定編碼,可以使用InputStreamReader和OutputStreamWriter

對象序列化

對象序列化是指將對象轉換為字節序列的過程,反序列化則是將字節序列恢復為對象的過程。通過對象序列化,我們可以將對象保存到文件中,或在網絡中傳輸對象。

Serializable接口

在Java中,要實現對象序列化,類必須實現Serializable接口。Serializable接口是一個標記接口,它沒有任何方法,只是用於標識該類可以被序列化。

語法

複製

public class User implements Serializable {
    // 類成員和方法
}

注意事項

  • 靜態成員變量不會被序列化,因為靜態成員屬於類,不屬於對象
  • transient關鍵字修飾的成員變量不會被序列化
  • 如果父類實現了Serializable接口,子類會自動繼承序列化能力
  • 如果父類沒有實現Serializable接口,子類實現了,那麼父類中的成員變量不會被序列化

ObjectOutputStream和ObjectInputStream

ObjectOutputStream和ObjectInputStream是用於對象序列化和反序列化的類,它們分別繼承自OutputStream和InputStream。

構造方法

複製

ObjectOutputStream(OutputStream out)
ObjectInputStream(InputStream in)

常用方法

複製

// ObjectOutputStream方法
void writeObject(Object obj) // 將對象寫入輸出流

// ObjectInputStream方法
Object readObject() // 從輸入流讀取對象

對象序列化案例

下面通過一個案例來演示對象的序列化和反序列化:

複製

import java.io.*;

// 實現Serializable接口
class User implements Serializable {
    private static final long serialVersionUID = 1L; // 序列化版本號

    private String name;
    private int age;
    private transient String password; // transient修飾的成員不會被序列化

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

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

public class SerializationDemo {
    public static void main(String[] args) {
        // 創建對象
        User user = new User("張三", 20, "123456");
        System.out.println("序列化前:" + user);

        // 對象序列化
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("user.ser"))) {
            oos.writeObject(user);
            System.out.println("對象序列化成功!");

        } catch (IOException e) {
            e.printStackTrace();
        }

        // 對象反序列化
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("user.ser"))) {
            User deserializedUser = (User) ois.readObject();
            System.out.println("反序列化後:" + deserializedUser);

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

運行結果

複製

序列化前:User{name='張三', age=20, password='123456'}
對象序列化成功!
反序列化後:User{name='張三', age=20, password='null'}

對象序列化流程

Java21天學習計劃 - 第九天:IO流與文件操作基礎_System_04

注意事項

  • 建議顯式聲明serialVersionUID,以保證版本兼容性
  • 被transient修飾的成員變量不會被序列化,反序列化後會被賦予默認值
  • 反序列化時,JVM會創建一個新對象,但不會調用構造方法
  • 如果對象中包含其他對象的引用,這些對象也必須是可序列化的

總結

今天我們學習了Java IO流與文件操作的基礎知識,主要內容包括:

  1. IO流概念與分類:IO流分為字節流和字符流,輸入流和輸出流,節點流和處理流。字節流適用於所有文件,字符流適用於文本文件。
  2. 文件操作:File類提供了文件和目錄的操作方法,可以創建、刪除、重命名文件,查詢文件屬性等。
  3. 字節流:FileInputStream/FileOutputStream用於文件的字節讀寫,BufferedInputStream/BufferedOutputStream可以提高讀寫效率。
  4. 字符流:FileReader/FileWriter用於文件的字符讀寫,BufferedReader/BufferedWriter提供了按行讀寫的功能,適合處理文本文件。
  5. 對象序列化:實現Serializable接口的類可以被序列化,通過ObjectOutputStream和ObjectInputStream可以實現對象的持久化存儲和網絡傳輸。

IO流是Java編程中非常重要的一部分,掌握IO流的使用對於文件操作、網絡編程等都至關重要。在實際開發中,我們需要根據具體需求選擇合適的流,並注意流的關閉和異常處理。

練習作業

  1. 使用字節流複製一個圖片文件,比較使用緩衝流和不使用緩衝流的效率差異。
  2. 使用字符流讀取一個文本文件,統計文件中單詞的數量。
  3. 創建一個Student類,實現Serializable接口,將Student對象序列化到文件中,然後再反序列化讀取。

通過今天的學習,相信大家已經對Java IO流有了基本的瞭解。明天我們將學習多線程編程,進一步提升我們的Java技能!