Java IO 體系不僅是流操作 API,更是設計模式的經典實戰場。本文深度解析序列化核心機制、serialVersionUID 作用、預設與自定義序列化策略,並從源碼層面剖析 Java IO 中裝飾器模式與適配器模式的巧妙應用。
| 概念 | 說明 |
|---|---|
| 序列化 | 將物件狀態轉為位元組序列 (持久化/網絡傳輸) |
| 反序列化 | 從位元組序列重建物件狀態 |
| 序列化協議 | Java 專有二進制格式,跨平台但非跨語言 |
| 適用場景 | RMI、HTTP Session 複製、分散式快取、檔案持久化 |
public class User implements Serializable {
private static final long serialVersionUID = 1L; // 強烈建議顯式聲明
private String name;
private int age;
private transient String password; // 不序列化
private static String company; // 不序列化
// 構造器、getter/setter...
}
public class UserV2 implements Externalizable {
private String name;
private int age;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
// 完全自定義寫入邏輯、加密、壓縮等
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = (String) in.readObject();
this.age = in.readInt();
}
// 必須有無參構造器!
public UserV2() {}
}
InvalidClassExceptionprivate static final long serialVersionUID = 1L; 手動維護,兼容性升級時保持不變序列化物件時,會遞歸序列化其引用的所有物件 (物件圖)。若引用鏈包含大型集合、巢狀集合,開銷極大。
| 修飾符 | 序列化行為 |
|---|---|
| transient | 跳過,反序列化後為默認值 (null/0/false) |
| static | 屬於類不屬於實例,跳過 |
Thread、Socket、FileDescriptor 等系統資源句柄writeObject/readObject 自定義處理public class ConnectionWrapper implements Serializable {
private transient Socket socket; // 不直接序列化 Socket
private String host;
private int port;
// 自定義寫入:只存 host/port
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 先執行預設邏寫入非 transient 欄位
out.writeUTF(host);
out.writeInt(port);
}
// 自定義讀取:重建 Socket 連接
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 先讀取非 transient 欄位
this.host = in.readUTF();
this.port = in.readInt();
this.socket = new Socket(host, port); // 重建連接
}
}
import java.io.*;
import java.util.*;
public class SerializationDemo {
private static final String FILE = "data.ser";
public static void main(String[] args) {
serialize();
deserialize();
}
static void serialize() {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(FILE))) {
// 1. 一般物件
User user = new User("Alice", 25, "secret123");
out.writeObject(user);
// 2. 基本類型
out.writeBoolean(true);
out.writeByte((byte) 65);
out.writeChar('中');
out.writeInt(20240101);
out.writeFloat(3.14f);
out.writeDouble(Math.PI);
// 3. 集合 (支援序列化)
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
out.writeObject(map);
System.out.println("序列化完成");
} catch (IOException e) {
e.printStackTrace();
}
}
static void deserialize() {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(FILE))) {
// 必須按寫入順序讀取!
User user = (User) in.readObject();
System.out.println("User: " + user);
System.out.println("Boolean: " + in.readBoolean());
System.out.println("Byte: " + (in.readByte() & 0xFF));
System.out.println("Char: " + in.readChar());
System.out.println("Int: " + in.readInt());
System.out.println("Float: " + in.readFloat());
System.out.println("Double: " + in.readDouble());
@SuppressWarnings("unchecked")
Map<String, String> map = (Map<String, String>) in.readObject();
map.forEach((k, v) -> System.out.println(k + " -> " + v));
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient String password;
private static String company = "TechCorp";
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 + ", company=" + company + "}";
}
}
角色對應:
| 角色 | InputStream 體系 | OutputStream 體系 | Reader 體系 | Writer 體系 |
|---|---|---|---|---|
| Component (抽象構件) | InputStream | OutputStream | Reader | Writer |
| ConcreteComponent (具體構件) | FileInputStream, ByteArrayInputStream, PipedInputStream | FileOutputStream, ByteArrayOutputStream, PipedOutputStream | FileReader, StringReader, CharArrayReader | FileWriter, StringWriter, CharArrayWriter |
| Decorator (抽象裝飾) | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
| ConcreteDecorator (具體裝飾) | BufferedInputStream, DataInputStream, LineNumberInputStream, PushbackInputStream | BufferedOutputStream, DataOutputStream, PrintStream | BufferedReader, LineNumberReader, PushbackReader | BufferedWriter, PrintWriter |
public class FilterInputStream extends InputStream {
protected volatile InputStream in; // 持有被裝飾對象引用
protected FilterInputStream(InputStream in) {
this.in = in;
}
// 核心:委派給被裝飾對象,可在前後增強
@Override
public int read() throws IOException {
return in.read(); // 透明轉發
}
@Override
public int read(byte[] b) throws IOException {
return in.read(b);
}
// 具體裝飾類擴展功能
// BufferedInputStream: 內部緩衝區,批量讀取減少系統調用
// DataInputStream: 增加 readInt/readUTF 等類型化讀取
// PushbackInputStream: 增加 unread() 退回字節能力
}
適配器模式 - 介面轉換
核心思想:將一個類的介面轉換為客戶端期望的另一個介面。
Java IO 中適配器實例
| 適配器類 | 適配目標 (Target) | 被適配者 (Adaptee) | 說明 |
|---|---|---|---|
| InputStreamReader | Reader (字符流) | InputStream (字節流) | 字節→字符,指定 Charset 解碼 |
| OutputStreamWriter | Writer (字符流) | OutputStream (字節流) | 字符→字節,指定 Charset 編碼 |
| ByteArrayInputStream | InputStream | byte[] | 位元組陣列 → 流介面 |
| ByteArrayOutputStream | OutputStream | byte[] (內部緩衝) | 流介面 → 位元組陣列 |
| StringReader | Reader | String | 字符串 → 字符流 |
| StringWriter | Writer | StringBuffer (內部) | 字符流 → 字符串 |
| FileInputStream | InputStream | FileDescriptor | 檔案描述符 → 流介面 |
public class InputStreamReader extends Reader {
private final StreamDecoder sd; // 內部解碼器
public InputStreamReader(InputStream in) {
this(in, Charset.defaultCharset());
}
public InputStreamReader(InputStream in, Charset cs) {
this.sd = StreamDecoder.forInputStreamReader(in, cs, -1);
}
@Override
public int read(char[] cbuf, int offset, int length) throws IOException {
return sd.read(cbuf, offset, length); // 委派給解碼器
}
@Override
public void close() throws IOException {
sd.close();
}
}
| 維度 | 裝飾器模式 | 適配器模式 |
|---|---|---|
| 目的 | 動態增加職責 (增強功能) | 介面轉換 (兼容介面) |
| 介面關係 | 裝飾者與被裝飾者介面相同 | 適配器實現目標介面,內部持有不同介面的被適配者 |
| 透明性 | 客戶端無感知 (完全透明) | 客戶端面向目標介面,知曉適配存在 |
| 組合方式 | 遞歸嵌套:Dec(Dec(Comp)) | 單層包裝:Adapter(Adaptee) |
| Java IO 體現 | BufferedInputStream, DataInputStream | InputStreamReader, ByteArrayInputStream |
許多類同時扮演兩種角色:
BufferedInputStream:裝飾器 (增加緩衝) + 適配器 (將 byte[] 緩衝區適配為 InputStream 介面)DataInputStream:裝飾器 (增加類型化讀取) + 適配器 (將基本類型讀取適配為流操作)Serializable、顯式聲明 serialVersionUID、transient 控制敏感/非序列化欄位、自定義 writeObject/readObject 處理資源重建FilterInputStream/OutputStream 為抽象裝飾,鏈式組合實現功能疊加 (緩衝、類型化、回退)InputStreamReader、ByteArrayInputStream 為典型代表深入理解 Java IO 設計模式,不僅能正確使用 API,更能在設計自定義流、封裝第三方庫、排查流關閉/編碼問題時遊刃有餘。