二進制序列化將對象轉換為字節流,用於文件持久化、網絡傳輸、進程間通信。Java 和 C# 均提供原生二進制序列化機制。本文通過完整代碼示例,演示雙語言的序列化/反序列化操作,並探討跨語言互操作的關鍵點與替代方案。
java.io.Serializable 標記接口:類實現該接口(無方法),聲明可序列化。ObjectOutputStream.writeObject(obj):將對象圖寫入字節流。ObjectInputStream.readObject():從字節流重建對象圖。serialVersionUID:顯式聲明版本號,防止類結構變更導致 InvalidClassException。import java.io.*;
public class JavaBinarySerializationDemo {
// 1. 簡單文件字節流複製(非對象序列化,僅演示流操作)
public static void copyFile(String src, String dest) throws IOException {
try (FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dest);
BufferedInputStream bin = new BufferedInputStream(in);
BufferedOutputStream bout = new BufferedOutputStream(out)) {
byte[] buffer = new byte[8192];
int len;
while ((len = bin.read(buffer)) != -1) {
bout.write(buffer, 0, len);
}
bout.flush();
System.out.println("文件複製完成:" + dest);
}
}
// 2. 對象序列化到文件
public static void serializeObject(Serializable obj, String filePath) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) {
oos.writeObject(obj);
System.out.println("對象序列化成功:" + filePath);
}
}
// 3. 文件反序列化為對象
public static Object deserializeObject(String filePath) throws IOException, ClassNotFoundException {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) {
Object obj = ois.readObject();
System.out.println("對象反序列化成功");
return obj;
}
}
// 測試實體類
static 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 static void main(String[] args) throws Exception {
// 文件複製測試
copyFile("src/main/resources/logo.gif", "target/logo_copy.gif");
// 對象序列化測試
User user = new User("Alice", 25, "secret123");
String userFile = "target/user.dat";
serializeObject(user, userFile);
User restored = (User) deserializeObject(userFile);
System.out.println("還原對象:" + restored); // password 為 null(transient)
}
}
[Serializable] 特性:標記類可序列化。System.Runtime.Serialization.Formatters.Binary.BinaryFormatter:核心序列化器(注意:.NET Core 3.0+ 標記為過時,不推薦新項目使用)。Serialize(Stream, object) / Deserialize(Stream):流式讀寫。using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable]
public class PlayManager
{
public string PlayerName { get; set; }
public int Score { get; set; }
// [NonSerialized] private string _token; // 不序列化字段
public void Save(string filePath)
{
try
{
using (FileStream fs = new FileStream(filePath, FileMode.Create))
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(fs, this);
}
Console.WriteLine("C# 序列化保存成功:" + filePath);
}
catch (Exception ex)
{
Console.WriteLine("序列化失敗:" + ex.Message);
}
}
public static PlayManager Load(string filePath)
{
try
{
using (FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate))
{
BinaryFormatter bf = new BinaryFormatter();
return (PlayManager)bf.Deserialize(fs);
}
}
catch (Exception ex)
{
Console.WriteLine("反序列化失敗:" + ex.Message);
return null;
}
}
}
// 測試
class Program
{
static void Main()
{
var mgr = new PlayManager { PlayerName = "Bob", Score = 999 };
string path = "playmanager.dat";
mgr.Save(path);
var loaded = PlayManager.Load(path);
Console.WriteLine($"載入:Name={loaded.PlayerName}, Score={loaded.Score}");
}
}
| 問題 | 說明 | 解決方案 |
|---|---|---|
| 格式不兼容 | Java ObjectOutputStream 與 C# BinaryFormatter 使用各自專有二進制格式,無法直接互讀。 |
雙方協議統一採用跨語言序列化標準。 |
| 類型系統差異 | 基本類型大小、字符編碼、日期時間表示、集合結構均不同。 | 使用Schema 驅動的序列化框架,由 IDL 定義數據結構,生成雙語言代碼。 |
| 版本演進 | 字段增減、類型變更需向前/向後兼容。 | 選擇支持 Schema 演進的格式(Protobuf、Avro、Thrift、MessagePack)。 |
| 安全性 | Java 反序列化漏洞(Gadget Chain)、C# BinaryFormatter 遠程代碼執行風險。 | 嚴禁反序列化不可信數據;遷移至安全框架,啟用類過濾器。 |
| 方案 | 特點 | 適用場景 | Java 生態 | C# 生態 |
|---|---|---|---|---|
| Protocol Buffers (Protobuf) | Google 出品,二進制、Schema 優先、高性能、體積小、支持 gRPC | 微服務 RPC、高吞吐內部通信、持久化 | protobuf-java, protobuf-gradle-plugin | Google.Protobuf, Grpc.Tools |
| Apache Avro | JSON Schema、動態類型、壓縮、Hadoop 生態標準 | 大數據管道、Kafka 消息、數據湖存儲 | avro, avro-maven-plugin | Apache.Avro, Microsoft.Hadoop.Avro |
| MessagePack | 「二進制 JSON」、無 Schema、自描述、極快 | 緩存、日誌、配置、輕量級 RPC | msgpack-java | MessagePack-CSharp |
| JSON (Jackson / System.Text.Json) | 文本、可讀、通用、Web API 標準 | 對外 REST API、配置文件、異構系統集成 | Jackson, Gson | System.Text.Json, Newtonsoft.Json |
Serializable + serialVersionUID;C# 新項目避開 BinaryFormatter,改用 System.Text.Json、MessagePack 或 Protobuf。Java 與 C# 原生二進制序列化雖然簡單易用,但僅限單語言內部使用。現代分佈式系統中,請拥抱跨語言、Schema 驅動、高性能、安全的序列化標準(Protobuf/Avro/MessagePack),從架構層面解決互操作與演進難題。