ObjectOutputStream / ObjectInputStream 提供物件序列化與反序列化能力,支援基本類型與實現 Serializable 或 Externalizable 介面的物件持久化存儲、網絡傳輸、進程間通訊。
只有實現 java.io.Serializable 或 java.io.Externalizable 介面的類,其實例才能被序列化/反序列化。
Serializable:標記介面,無方法,JVM 自動處理序列化細節Externalizable:需手動實現 writeExternal/readExternal,完全控制格式,效能更高| 方法 | 說明 |
|---|---|
writeObject(Object obj) | 寫入物件(含圖引用處理) |
writeUnshared(Object obj) | 寫入「非共享」物件,反序列化時強制新建實例 |
defaultWriteObject() | 在 writeObject 中調用,執行預設序列化邏輯 |
putFields() | 獲取 PutField 手動控制欄位寫入 |
writePrimitive 系列 | writeInt、writeLong、writeUTF 等基本類型 |
flush()/close() | 刷新緩衝區、關閉流(會自動 flush) |
reset() | 重置已寫入物件引用表,後續寫入視為新物件 |
useProtocolVersion(int) | 指定序列化協議版本 |
| 方法 | 說明 |
|---|---|
readObject() | 讀取物件(自動處理引用共享) |
readUnshared() | 讀取「非共享」物件,強制新建實例 |
defaultReadObject() | 在 readObject 中調用,執行預設反序列化 |
readFields() | 獲取 GetField 手動控制欄位讀取 |
readPrimitive 系列 | readInt、readLong、readUTF 等 |
registerValidation(ObjectInputValidation, int) | 註冊驗證回調,反序列化完成後執行 |
available()/skipBytes() | 流操作輔助 |
class Box implements Serializable {
private static final long serialVersionUID = 1L; // 強烈建議顯式聲明
private int width;
private int height;
private String name;
public Box(String name, int width, int height) {
this.name = name; this.width = width; this.height = height;
}
@Override public String toString() {
return "[" + name + ": (" + width + ", " + height + ") ]";
}
}
public static void testWrite() {
try (ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("box.tmp"))) {
// 基本類型
out.writeBoolean(true);
out.writeByte((byte) 65);
out.writeChar('a');
out.writeInt(20131015);
out.writeFloat(3.14F);
out.writeDouble(1.414D);
// 集合物件
Map<String, String> map = new HashMap<>();
map.put("one", "red");
map.put("two", "green");
map.put("three", "blue");
out.writeObject(map);
// 自定義物件
Box box = new Box("desk", 80, 48);
out.writeObject(box);
} catch (Exception ex) { ex.printStackTrace(); }
}
public static void testRead() {
try (ObjectInputStream in = new ObjectInputStream(
new FileInputStream("box.tmp"))) {
System.out.printf("boolean:%b%n", in.readBoolean());
System.out.printf("byte:%d%n", in.readByte() & 0xff);
System.out.printf("char:%c%n", in.readChar());
System.out.printf("int:%d%n", in.readInt());
System.out.printf("float:%f%n", in.readFloat());
System.out.printf("double:%f%n", in.readDouble());
// 讀取 Map
@SuppressWarnings("unchecked")
Map<String, String> map = (Map<String, String>) in.readObject();
map.forEach((k, v) -> System.out.printf("%-6s -- %s%n", k, v));
// 讀取 Box
Box box = (Box) in.readObject();
System.out.println("box: " + box);
} catch (Exception e) { e.printStackTrace(); }
}
boolean:true byte:65 char:a int:20131015 float:3.140000 double:1.414000 three -- blue two -- green one -- red box: [desk: (80, 48) ]
InvalidClassExceptionprivate static final long serialVersionUID = 1L;private transient String password; // 不參與序列化,反序列化後為 null
private transient Logger logger; // 非序列化資源
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 執行預設邏輯
out.writeObject(encrypt(password)); // 額外處理敏感欄位
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
this.password = decrypt((String) in.readObject());
}
public class Data implements Externalizable {
private int value;
@Override public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(value * 2); // 自定義編碼
}
@Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.value = in.readInt() / 2;
}
}
Serializable → 子類自動可序列化InvalidClassException反序列化任意類別是重大安全漏洞入口(如 Apache Commons Collections Gadget 鏈)。
防範措施:
ObjectInputFilter 白名單過濾reset() 定期清理引用表防止記憶體洩漏Externalizable 或 Protobuf/Kryonew BufferedOutputStream(new FileOutputStream(file), 64*1024)ObjectOutputStream/InputStream 是 Java 內建序列化機制的核心,掌握以下要點即可靈活應用:
Serializable 並聲明 serialVersionUIDwriteObject/readObject 配合 defaultWriteObject/readObject 自定義邏輯transient、Externalizable、writeUnshared/readUnshared 進階用法