IO流概念與分類
在Java編程中,我們經常需要讀取或寫入文件、網絡數據等,這些操作都離不開IO流。IO(Input/Output)流是Java中用於處理輸入輸出的核心機制,它可以將數據從一個地方傳輸到另一個地方,比如從文件到程序,或者從程序到網絡。
IO流的體系結構
Java的IO流體系非常龐大,但整體上可以分為兩大派系:字節流和字符流。字節流以字節為單位處理數據,適用於所有類型的文件;字符流以字符為單位處理數據,專門用於文本文件。下面是IO流的體系結構圖:
從圖中可以看出,IO流主要分為以下幾類:
- 按數據流向分:
- 輸入流(InputStream/Reader):從數據源讀取數據到程序
- 輸出流(OutputStream/Writer):從程序寫入數據到目的地
- 按數據單位分:
- 字節流(InputStream/OutputStream):以字節為單位(8位)
- 字符流(Reader/Writer):以字符為單位(16位Unicode)
- 按流的角色分:
- 節點流(低級流):直接與數據源相連,如FileInputStream
- 處理流(高級流/裝飾流):包裝節點流,增強功能,如BufferedInputStream
字節流與字符流對比
字節流和字符流是IO流體系中最核心的兩種流,它們的主要區別如下:
表格
|
類型 |
輸入流 |
輸出流 |
特點 |
|
字節流 |
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毫秒
流程圖:
文件複製的流程如下所示:
注意事項:
- 使用緩衝流可以顯著提高讀寫效率,推薦優先使用
- 流操作完成後必須關閉,通常在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'}
對象序列化流程:
注意事項:
- 建議顯式聲明serialVersionUID,以保證版本兼容性
- 被transient修飾的成員變量不會被序列化,反序列化後會被賦予默認值
- 反序列化時,JVM會創建一個新對象,但不會調用構造方法
- 如果對象中包含其他對象的引用,這些對象也必須是可序列化的
總結
今天我們學習了Java IO流與文件操作的基礎知識,主要內容包括:
- IO流概念與分類:IO流分為字節流和字符流,輸入流和輸出流,節點流和處理流。字節流適用於所有文件,字符流適用於文本文件。
- 文件操作:File類提供了文件和目錄的操作方法,可以創建、刪除、重命名文件,查詢文件屬性等。
- 字節流:FileInputStream/FileOutputStream用於文件的字節讀寫,BufferedInputStream/BufferedOutputStream可以提高讀寫效率。
- 字符流:FileReader/FileWriter用於文件的字符讀寫,BufferedReader/BufferedWriter提供了按行讀寫的功能,適合處理文本文件。
- 對象序列化:實現Serializable接口的類可以被序列化,通過ObjectOutputStream和ObjectInputStream可以實現對象的持久化存儲和網絡傳輸。
IO流是Java編程中非常重要的一部分,掌握IO流的使用對於文件操作、網絡編程等都至關重要。在實際開發中,我們需要根據具體需求選擇合適的流,並注意流的關閉和異常處理。
練習作業:
- 使用字節流複製一個圖片文件,比較使用緩衝流和不使用緩衝流的效率差異。
- 使用字符流讀取一個文本文件,統計文件中單詞的數量。
- 創建一個Student類,實現Serializable接口,將Student對象序列化到文件中,然後再反序列化讀取。
通過今天的學習,相信大家已經對Java IO流有了基本的瞭解。明天我們將學習多線程編程,進一步提升我們的Java技能!