Java序列化方案

什么是序列化?

序列化是将对象的状态转换为字节流,以便在网络上传输或在磁盘上持久化存储。

常见的Java序列化方案

Java 原生序列化

Java 原生序列化通过实现 java.io.Serializable 接口,使对象可以被序列化和反序列化。这是最基本的序列化机制,由 Java 标准库提供支持。

优点

  • 简单易用:只需实现 Serializable 接口,无需额外配置。
  • 内置支持:Java 标准库自带支持,无需引入第三方库。

缺点

  • 性能较低:序列化和反序列化速度较慢,生成的字节流较大。
    • 性能差的原因:使用反射机制来读取对象的字段。

使用示例

  1. 定义可序列化的类
import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L; // 推荐显式声明

    private String name;
    private int age;

    
    // 构造方法
    public Person() {}
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // Getters 和 Setters
    // toString
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  1. 序列化和返序列化
// 创建对象
Person person = new Person("张三", 30);
// 将对象写入到文件中
try (FileOutputStream fileOut = new FileOutputStream("person.ser");
    ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
    out.writeObject(person);
    System.out.println("对象已序列化并保存到 person.ser 文件中");
} catch (IOException i) {
    i.printStackTrace();
}

//从文件反序列化对象
try (FileInputStream fileIn = new FileInputStream("person.ser");
    ObjectInputStream in = new ObjectInputStream(fileIn)) {
    person = (Person) in.readObject();
    System.out.println("从 person.ser 文件中反序列化得到对象:");
    System.out.println(person);
} catch (IOException | ClassNotFoundException i) {
    i.printStackTrace();
}
  • 输出结果
对象已序列化并保存到 person.ser 文件中
从 person.ser 文件中反序列化得到对象:
Person{name='张三', age=30}

JSON 序列化

常用库

  • Jackson
  • Gson
  • FastJSON
  • JSON-B
  • JSON-P

优点

  • 可读性高:生成的 JSON 格式易于阅读和调试。
  • 跨语言支持:JSON 是一种通用的数据交换格式,适用于不同编程语言之间的数据传输。
  • 灵活性:支持自定义序列化和反序列化规则。

缺点

  • 性能较低:相比二进制序列化,JSON 的解析速度较慢,生成的字节流较大。
  • 类型安全性较低:需要额外处理类型转换问题。

使用示例

Jackson

  1. 引入依赖
<dependencies>
    <!-- Jackson Databind -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.2</version>
    </dependency>
</dependencies>
  1. 使用 Jackson 进行序列化和反序列化
ObjectMapper mapper = new ObjectMapper();
Person person = new Person("Alice", 30);

// 序列化
String jsonString = mapper.writeValueAsString(person);
System.out.println(jsonString);

// 反序列化
Person deserializedPerson = mapper.readValue(jsonString, Person.class);
System.out.println(deserializedPerson.getName());
  • 输出结果
{"name":"Alice","age":30}
Alice
30
  • 注意事项
    • 如果 Person 没有无参构造方法,在反序列化时,会报以下错误
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.example.Person` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)"{"name":"Alice","age":30}"; line: 1, column: 2]
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1915)
	at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:414)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1360)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1424)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:352)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185)
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4825)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3772)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3740)
	at org.example.Main.main(Main.java:18)

Gson

  1. 引入依赖
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
</dependency>
  1. 使用 Gson 进行序列化和反序列化
Gson gson = new GsonBuilder().setPrettyPrinting().create();
Person person = new Person("Alice", 30);
// 序列化
String jsonString = gson.toJson(person);
System.out.println(jsonString);
//output: {"name":"Alice","age":30}

// 反序列化
Person deserializedPerson = gson.fromJson(jsonString, Person.class);
System.out.println(deserializedPerson.getName());
  • 输出结果
{
  "name": "Alice",
  "age": 30
}
Alice

FastJson

  1. 引入依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>
  1. 使用 FastJson 进行序列化和反序列化
Person person = new Person("Alice", 30);
        
// 序列化
String jsonString = JSON.toJSONString(person);
System.out.println(jsonString);

// 反序列化
Person deserializedPerson = JSON.parseObject(jsonString, Person.class);
System.out.println(deserializedPerson.getName());
  • 输出结果
{"age":30,"name":"Alice"}
Alice

JSON-B (JSON Binding)

  • 概述:Java EE 的标准 JSON 绑定 API,用于将 Java 对象转换为 JSON。
  • 优点:标准化,易于集成。

JSON-B 使用示例

  1. 引入 JSON-B 依赖(以 Eclipse Yasson 实现为例)
<dependency>
    <groupId>org.eclipse</groupId>
    <artifactId>yasson</artifactId>
    <version>2.0.3</version>
</dependency>
  1. 定义需要序列化的对象
public class Person implements Serializable{
    @JsonbProperty("full_name")
    private String name;
    private int age;
}
// 构造函数
// Getter and Setter
// toString
  1. 使用 JSON-B 进行序列化和反序列化
// 将 Person 序列化为 json 字符串
Person person = new Person("张三", 30);
Jsonb jsonb = JsonbBuilder.create();

String jsonString = jsonb.toJson(person);
System.out.println(jsonString);
// 输出:{"full_name":"张三","age":30}

// 将 json 字符串反序列化为 Person 对象
Person deserializedPerson = jsonb.fromJson(jsonString, Person.class);
System.out.println(deserializedPerson.getName());
  • 输出结果
{"age":30,"full_name":"张三"}
Deserialized Person:
Name: 张三
Age: 30

JSON-P (JSON Processing)

  • 概述:用于处理 JSON 数据的标准 API,提供流式和树式的处理方式。
  • 优点:灵活高效,适合处理大型 JSON 数据。

JSON-P 使用示例

  1. 引入了 JSON-P 的依赖
<dependency>
    <groupId>javax.json</groupId>
    <artifactId>javax.json-api</artifactId>
    <version>1.1.4</version>
</dependency>
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.json</artifactId>
    <version>1.1.4</version>
</dependency>
  1. 使用 JSON-P 进行序列化和反序列化
// 序列化
JsonObject jsonObject = Json.createObjectBuilder()
    .add("name", "赵六")
    .add("age", 35)
    .build();

String jsonString = jsonObject.toString();

// 反序列化
JsonReader jsonReader = Json.createReader(new StringReader(jsonString));
JsonObject jsonObject = jsonReader.readObject();
String name = jsonObject.getString("name");
int age = jsonObject.getInt("age");

  • 输出结果
Name: 赵六
Age: 35

XML 序列化

常用库

  • JAXB (Java Architecture for XML Binding)

优点

  • 标准化:XML 是一种标准的数据交换格式,广泛应用于企业级应用。
  • 可读性高:类似于 JSON,XML 也具有良好的可读性。

缺点

  • 冗长:相比 JSON,XML 的标签更冗长,导致生成的字节流更大。
  • 解析复杂:XML 的解析过程相对复杂,性能较低。

使用示例

  1. 引入依赖
<dependencies>
    <!-- JAXB API -->
    <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.3.1</version>
    </dependency>
    <!-- JAXB 实现 -->
    <dependency>
        <groupId>com.sun.xml.bind</groupId>
        <artifactId>jaxb-impl</artifactId>
        <version>2.3.3</version>
    </dependency>
</dependencies>

  1. 定义可序列化的类
@XmlRootElement
public class Person implements Serializable {
    private static final long serialVersionUID = 1L; // 推荐显式声明

    private String name;
    private int age;

    public Person() {}
    // 构造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // Getters 和 Setters
    // toString
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 需要在可序列化类中加上 @XmlRootElement 注解
  • 需要设置无参构造方法
  1. 使用 JAXB 进行序列化和反序列化
JAXBContext context = JAXBContext.newInstance(Person.class);
Marshaller marshaller = context.createMarshaller();
Unmarshaller unmarshaller = context.createUnmarshaller();

Person person = new Person("Bob", 25);
StringWriter writer = new StringWriter();

// 序列化
marshaller.marshal(person, writer);
String xmlString = writer.toString();
System.out.println(xmlString);

// 反序列化
Person deserializedPerson = (Person) unmarshaller.unmarshal(new StringReader(xmlString));
System.out.println(deserializedPerson.getName());
  • 输出结果
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><person><age>25</age><name>Bob</name></person>
Bob

二进制序列化

二进制序列化是一种将对象的状态转换为二进制格式的过程,以便能够在存储介质(如文件、数据库)中保存,或通过网络传输,并在需要时将其反序列化(即还原)回原始对象的过程。

优点

  • 高性能:序列化和反序列化速度快,生成的字节流紧凑。
  • 跨语言支持:支持多种编程语言,适用于分布式系统。
  • 类型安全性高:通过定义明确的协议或模式,确保数据的类型安全。

缺点

  • 复杂性:相比 JSON 和 XML,二进制序列化框架通常需要更多的配置和学习成本。
  • 可读性低:生成的二进制数据不可读,不便于调试。

常用框架

Kryo

Kryo 介绍

Kryo 是由 Esoteric Software 开发并维护的开源序列化框架,旨在提供比 Java 内置序列化机制更高效的性能。它广泛应用于需要高效数据传输和存储的场景,如分布式系统、网络通信和大数据处理等。

主要特点

  1. 高性能:Kryo 的序列化和反序列化速度远超 Java 的内置序列化机制,适合对性能要求较高的应用。
  2. 紧凑的序列化格式:生成的二进制数据较小,有助于减少网络传输和存储空间的开销。
  3. 支持多种数据类型:不仅支持基本数据类型,还支持复杂的自定义对象、集合、枚举、日期等。
  4. 对象引用处理:能够处理对象图中的循环引用,避免重复序列化相同对象。
  5. 可扩展性:允许开发者自定义序列化器,以满足特定类型或复杂对象的序列化需求。
  6. 跨平台支持:虽然 Kryo 主要用于 Java,但也有其他语言的实现或兼容方案,便于在多语言环境中使用。
Kryo 使用示例
  1. 在 maven 中添加 Kryo 依赖
<dependencies>
    <dependency>
        <groupId>com.esotericsoftware</groupId>
        <artifactId>kryo</artifactId>
        <version>5.3.0</version>
    </dependency>
</dependencies>
  1. 定义可序列化的类
public class Person implements Serializable {
    private static final long serialVersionUID = 1L; // 推荐显式声明

    private String name;
    private int age;

    public Person() {}
    // 构造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // Getters 和 Setters
    // toString
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 需要设置无参构造方法
  1. 使用 Kryo 进行序列化和反序列化
// 创建 Kryo 实例
Kryo kryo = new Kryo();
// 注册类
kryo.register(Person.class);

// 创建一个 Person 对象
Person person = new Person("张三", 30);
System.out.println("原始对象: " + person);

// 序列化对象到字节数组
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos);
kryo.writeObject(output, person);
output.close();

byte[] bytes = baos.toByteArray();
System.out.println("序列化后的字节数组长度: " + bytes.length);

// 反序列化字节数组回对象
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Input inputStream = new Input(bais);
Person deserializedPerson = kryo.readObject(inputStream, Person.class);
inputStream.close();

System.out.println("反序列化后的对象: " + deserializedPerson);

  • 输出结果
原始对象: Person{name='张三', age=30}
序列化后的字节数组长度: 8
反序列化后的对象: Person{name='张三', age=30}

Protobuf

Protobuf 介绍

Protocol Buffers(简称 Protobuf)是由 Google 开发的一种高效、语言中立、平台中立、可扩展的结构化数据序列化机制。它主要用于在不同的系统之间交换数据,尤其适用于分布式系统和微服务架构。Protobuf 通过定义数据结构的“消息格式”来实现数据的序列化和反序列化,比传统的文本格式(如 JSON 或 XML)在速度和数据大小上具有显著优势。

主要特性

  • 高效的二进制格式:Protobuf 使用紧凑的二进制格式进行数据编码,能够显著减少数据传输的大小,并提高解析速度。

  • 跨语言支持:Protobuf 支持多种编程语言,包括但不限于 C++, Java, Python, Go, C#, Ruby 等,方便在不同技术栈的系统之间进行数据交换。

  • 明确的接口定义:通过 .proto 文件定义数据结构,确保各方对数据格式的理解一致,减少接口不兼容的问题。

  • 向前和向后兼容:Protobuf 设计允许在不破坏现有系统的情况下,向消息中添加新的字段,增强系统的可扩展性。

  • 自动代码生成:使用 protoc 编译器,可以根据 .proto 文件自动生成各语言的类和方法,简化开发流程。

Protobuf 使用示例
  1. 安装 Protocol Buffers 编译器

    • 前往 Protocol Buffers Releases 页面(https://github.com/protocolbuffers/protobuf/releases),根据操作系统下载对应版本的 protoc压缩包,例如:protoc-29.0-rc-1-win64.zip。解压下载的压缩包,并将 bin 目录添加到系统的环境变量 PATH 中,以便在终端中全局调用。
  2. 在 IDEA 安装 Proto Buffer 插件
    image

  3. 定义 person.proto 文件,用于定义数据结构。

syntax = "proto3";

option java_package = "org.example.protobuf";
option java_outer_classname = "PersonProto";

message Person {
    int32 id = 1;
    string name = 2;
    string email = 3;
}

  1. 生成 Java 类
protoc --java_out=./src/main/java person.proto
  • 命令会在 org.example.protobuf 包下生成 PersonProto.java 文件
  1. 在 maven 中添加 Protobuf 依赖
<dependencies>
   <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>4.29.0-RC1</version>
    </dependency>
</dependencies>
  1. 使用 Protobuf 进行序列化和反序列化
// 创建一个 Person 对象
Person person = Person.newBuilder()
    .setId(1)
    .setName("张三")
    .setEmail("zhangsan@example.com")
    .build();

// 序列化到文件
try (FileOutputStream output = new FileOutputStream("person.ser")) {
    person.writeTo(output);
    System.out.println("序列化成功,数据写入 person.ser 文件");
} catch (IOException e) {
    e.printStackTrace();
}

// 从文件反序列化
try (FileInputStream input = new FileInputStream("person.ser")) {
    Person deserializedPerson = Person.parseFrom(input);
    System.out.println("反序列化成功:");
    System.out.println("ID: " + deserializedPerson.getId());
    System.out.println("Name: " + deserializedPerson.getName());
    System.out.println("Email: " + deserializedPerson.getEmail());
} catch (IOException e) {
    e.printStackTrace();
}

  • 输出结果
序列化成功,数据写入 person.ser 文件
反序列化成功:
ID: 1
Name: 张三
Email: zhangsan@example.com

Hessian

Hessian 介绍

Apache Hessian 是一个轻量级的二进制Web服务协议,旨在简化不同编程语言之间的远程通信。由Caucho Technology开发并捐赠给Apache软件基金会,Hessian 提供了一种高效、简洁的方式来序列化和反序列化数据,使得客户端和服务器之间的数据交换更加快速和可靠。

主要特点

  • 二进制协议:与基于文本的协议(如XML-RPC或JSON)相比,Hessian 使用二进制格式进行数据传输,减少了数据包的大小,提高了传输效率。

  • 跨语言支持:Hessian 提供多种编程语言的实现,包括Java、C#、Python、Ruby等,使得不同技术栈的系统能够轻松集成和通信。

  • 简单易用:Hessian 的接口设计简洁,易于理解和使用。开发者无需复杂的配置即可快速上手。

  • 高性能:由于其轻量级和高效的序列化机制,Hessian 在处理大量数据和高并发请求时表现出色。

  • 支持复杂数据类型:Hessian 能够处理复杂的数据结构,如对象、列表、映射等,满足各种应用场景的需求。

Hessian 使用示例
  1. 在 maven 中添加 Hessian 依赖
<dependency>
    <groupId>com.caucho</groupId>
    <artifactId>hessian</artifactId>
    <version>4.0.66</version>
</dependency>
  1. 使用 Hessian 进行序列化和反序列化
Person person = new Person("Alice", 30);

try {
    // 序列化到数组
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
	HessianOutput hessianOutput = new HessianOutput(bos);
    hessianOutput.writeObject(person);
    byte[] serializedBytes = bos.toByteArray();
    
    // 反序列化字节数组回对象
    ByteArrayInputStream bais = new ByteArrayInputStream(serializedBytes);
    HessianInput hessianInput = new HessianInput(bais);
    Person deserializedPerson = (Person) hessianInput.readObject();
    
} catch (IOException e) {
    e.printStackTrace();
}

  • 输出结果
反序列化成功:
Name: Alice
Age: 30
  • 如果报以下错误,则通常是由于 Java 模块系统(自 Java 9 引入)对反射访问的限制导致,需要在 JVM 启动参数,允许 Hessian 进行反射访问:--add-opens java.base/java.lang=ALL-UNNAMED
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make private java.lang.StackTraceElement() accessible: module java.base does not "opens java.lang" to unnamed module @722c41f4

Thrift

Thrift 介绍

Apache Thrift 是一个高效、可扩展的跨语言服务开发框架,最初由Facebook开发,现已成为Apache软件基金会的顶级项目。Thrift的设计目标是简化不同编程语言之间的通信和数据交换,使开发者能够轻松构建分布式系统和微服务架构。

主要特点

  • 接口定义语言(IDL):Thrift 提供了一种简洁的IDL,用于定义服务接口和数据结构。通过编写 .thrift 文件,开发者可以描述服务的功能和数据类型。IDL 文件可以自动生成多种编程语言的代码,包括 Java、C++、Python、Go、Ruby、PHP 等,极大地减少了手动编写重复代码的工作量。
  • 跨语言支持:支持多种编程语言,使得不同语言编写的服务能够无缝协作。这对于采用多语言技术栈的项目尤为重要。例如,可以用Java编写服务端,用Python编写客户端,确保两者之间的通信顺畅。
  • 高效的二进制协议:Thrift 提供多种传输协议,如二进制协议(Binary Protocol)、JSON协议等。二进制协议因其高效性和紧凑性,常用于对性能要求较高的场景。支持多种传输层,如基于TCP的传输、基于HTTP的传输等,提供灵活的通信方式。
  • 可插拔的传输层和协议:开发者可以根据具体需求选择或定制传输层和协议,提升系统的灵活性和可扩展性。例如,可以在同一个服务中同时支持二进制和JSON协议,以适应不同的客户端需求。
Thrift 使用示例

安装 Apache Thrift

定义 Thrift IDL 文件

namespace java com.example.thrift

struct User {
  1: i32 id,
  2: string name,
  3: string email
}

使用 Thrift 编译器生成 Java 代码

thrift --gen java example.thrift

在maven 中引入 thrift 依赖

<dependencies>
    <dependency>
        <groupId>org.apache.thrift</groupId>
        <artifactId>libthrift</artifactId>
        <version>0.21.0</version>
    </dependency>
</dependencies>

使用 thrift 进行序列化和反序列化

// 创建 User 对象
User user = new User();
user.setId(1);
user.setName("李四");
user.setEmail("lisi@example.com");

// 序列化
TSerializer serializer = new TSerializer(new TBinaryProtocol.Factory());
byte[] serializedData = serializer.serialize(user);
System.out.println("Serialized data: " + java.util.Arrays.toString(serializedData));

// 反序列化
User deserializedUser = new User();
TDeserializer deserializer = new TDeserializer(new TBinaryProtocol.Factory());
deserializer.deserialize(deserializedUser, serializedData);

System.out.println("Deserialized User:");
System.out.println("ID: " + deserializedUser.getId());
System.out.println("Name: " + deserializedUser.getName());
System.out.println("Email: " + deserializedUser.getEmail());
posted @ 2024-10-16 13:12  Jacob-Chen  阅读(150)  评论(0)    收藏  举报