杰瑞科技汇

Java对象如何高效转byte数组?

这个过程的核心技术叫做 序列化(Serialization),序列化就是将 Java 对象及其状态信息转换为可以存储或传输的字节流的过程,对应的,从字节流恢复成 Java 对象的过程叫做 反序列化(Deserialization)

Java对象如何高效转byte数组?-图1
(图片来源网络,侵删)

下面我将详细介绍几种主要的方法,从最基础到最推荐的方式。


使用 Java 原生序列化(Serializable 接口)

这是 Java 语言内置的、最基础的序列化机制,几乎所有 Java 对象都可以通过实现 java.io.Serializable 接口来完成序列化。

步骤:

  1. 让对象类实现 Serializable 接口: 这个接口是一个标记接口,它本身不包含任何方法,它的作用是告诉 JVM 这个类的对象可以被序列化。

  2. 使用 ObjectOutputStream 进行序列化: 将对象写入一个输出流,这个输出流可以被重定向到文件、网络套接字或字节数组。

    Java对象如何高效转byte数组?-图2
    (图片来源网络,侵删)
  3. 使用 ObjectInputStream 进行反序列化: 从输入流中读取字节,并将其转换回原始的 Java 对象。

示例代码:

定义可序列化的对象类

import java.io.Serializable;
// 1. 让类实现 Serializable 接口
public 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 != null ? "******" : "null") + '\'' +
                '}';
    }
}

编写转换工具类

import java.io.*;
public class SerializableUtils {
    /**
     * 将对象序列化为字节数组
     * @param obj 要序列化的对象
     * @return 字节数组
     * @throws IOException 如果发生 I/O 错误
     */
    public static byte[] serialize(Object obj) throws IOException {
        if (obj == null) {
            return null;
        }
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            // 2. 使用 ObjectOutputStream 将对象写入字节数组输出流
            oos.writeObject(obj);
            oos.flush();
            return bos.toByteArray();
        }
    }
    /**
     * 从字节数组反序列化对象
     * @param bytes 字节数组
     * @return 反序列化后的对象
     * @throws IOException 如果发生 I/O 错误
     * @throws ClassNotFoundException 如果类找不到
     */
    public static <T> T deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
             ObjectInputStream ois = new ObjectInputStream(bis)) {
            // 3. 使用 ObjectInputStream 从字节数组输入流中读取对象
            @SuppressWarnings("unchecked")
            T obj = (T) ois.readObject();
            return obj;
        }
    }
}

测试

Java对象如何高效转byte数组?-图3
(图片来源网络,侵删)
public class Main {
    public static void main(String[] args) {
        User user = new User("Alice", 30, "123456");
        try {
            // 对象 -> byte[]
            byte[] userBytes = SerializableUtils.serialize(user);
            System.out.println("序列化成功,字节数组长度: " + userBytes.length);
            // byte[] -> 对象
            User deserializedUser = SerializableUtils.deserialize(userBytes);
            System.out.println("反序列化成功: " + deserializedUser);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

优点:

  • 简单易用:Java 标准库提供,无需额外依赖。
  • 通用性强:适用于任何实现了 Serializable 接口的 Java 对象。

缺点:

  • 性能较差:序列化后的字节数组通常比较大,序列化和反序列化的速度相对较慢。
  • 安全性问题:反序列化过程可能存在安全漏洞(如 Log4j 漏洞),如果反序列化的数据来源不可信,有被攻击的风险。
  • 版本兼容性:当类结构(如增删字段)发生变化时,serialVersionUID 必须妥善处理,否则可能导致反序列化失败。
  • 无法序列化某些对象:不能序列化 statictransient 字段,也不能序列化某些系统级对象(如 Thread, Socket 等)。

使用第三方库(强烈推荐)

在实际开发中,由于原生序列化的种种缺点,我们更倾向于使用高性能、跨语言、更安全的第三方库,目前最主流的选择是 JacksonGson,它们通常使用 JSON 格式作为中间表示,然后再将 JSON 转换为字节数组(通常是 UTF-8 编码的字节)。

这里以 Jackson 为例,它非常流行且性能卓越。

步骤:

  1. 添加 Jackson 依赖: 如果你使用 Maven,在 pom.xml 中添加:

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.2</version> <!-- 使用最新版本 -->
    </dependency>
  2. 使用 ObjectMapper 进行转换ObjectMapper 是 Jackson 库的核心类,负责对象和 JSON 之间的转换。

示例代码:

定义 POJO (Plain Old Java Object)

// POJO 不需要实现任何特殊接口,但必须有 getter/setter 或是 public 字段
public class User {
    private String name;
    private int age;
    private String password;
    // 必须有无参构造函数
    public User() {
    }
    public User(String name, int age, String password) {
        this.name = name;
        this.age = age;
        this.password = password;
    }
    // Getter 和 Setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", password='" + (password != null ? "******" : "null") + '\'' +
                '}';
    }
}

编写转换工具类

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JacksonUtils {
    // 创建一个全局的 ObjectMapper 实例,避免重复创建的开销
    private static final ObjectMapper objectMapper = new ObjectMapper();
    /**
     * 将对象转换为 JSON 字节数组
     * @param obj 要转换的对象
     * @return JSON 字节数组
     */
    public static byte[] serialize(Object obj) {
        if (obj == null) {
            return null;
        }
        try {
            // ObjectMapper.writeValueAsString() 也可以得到 JSON 字符串,但 writeValueAsBytes() 更直接
            return objectMapper.writeValueAsBytes(obj);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Jackson 序列化失败", e);
        }
    }
    /**
     * 从 JSON 字节数组反序列化对象
     * @param bytes JSON 字节数组
     * @param clazz 目标对象的 Class 对象
     * @return 反序列化后的对象
     */
    public static <T> T deserialize(byte[] bytes, Class<T> clazz) {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        try {
            return objectMapper.readValue(bytes, clazz);
        } catch (IOException e) {
            throw new RuntimeException("Jackson 反序列化失败", e);
        }
    }
}

测试

public class Main {
    public static void main(String[] args) {
        User user = new User("Bob", 25, "secret");
        // 对象 -> byte[]
        byte[] userBytes = JacksonUtils.serialize(user);
        System.out.println("序列化成功,JSON 字节数组长度: " + userBytes.length);
        System.out.println("JSON 内容: " + new String(userBytes)); // 打印 JSON 字符串
        // byte[] -> 对象
        User deserializedUser = JacksonUtils.deserialize(userBytes, User.class);
        System.out.println("反序列化成功: " + deserializedUser);
    }
}

优点:

  • 高性能:速度远超原生序列化,体积更小。
  • 跨语言:JSON 是一种通用的数据格式,可以被几乎所有编程语言解析,非常适合异构系统集成。
  • 可读性好:JSON 文本格式清晰,便于调试和查看。
  • 灵活性高:通过注解(如 @JsonIgnore)可以灵活控制哪些字段需要被序列化/反序列化。
  • 安全性更高:反序列化过程比原生 Java 序列化更安全。

缺点:

  • 需要引入外部库:对于小型项目可能觉得是个“负担”。
  • 不能序列化所有对象:同样,transientstatic 字段不会被处理,对于一些复杂的、非 POJO 的对象,可能需要额外的配置。

总结与选择建议

特性 Java 原生序列化 (Serializable) Jackson / Gson (JSON 序列化)
依赖 无需外部依赖 需要引入第三方库
格式 Java 特定二进制格式 JSON (文本)
性能 较慢,体积大 快,体积小
可读性 不可读(二进制) 可读(文本)
跨语言 否,仅限 Java 是,JSON 是通用标准
安全性 较低,有反序列化漏洞风险 较高
易用性 简单,实现接口即可 需要调用 API,但 API 设计直观
适用场景 - 简单的、仅限 Java 环境内的对象传递
- RMI (远程方法调用) 等特定 Java 技术栈
- 快速原型验证
- 强烈推荐:Web API 开发(HTTP Body)
- 微服务之间的通信
- 对象持久化到文件/数据库
- 需要跨语言交互的场景
  • 除非有特殊原因(如使用 RMI 或遗留系统),否则强烈推荐使用 Jackson 或 Gson 等第三方库。
  • 对于绝大多数现代应用,尤其是 Web 开发和微服务架构,Jackson 是事实上的标准选择,它在性能、易用性和功能上都表现优异。
  • 原生序列化机制更多存在于 Java 历史代码和特定技术中,新的项目应尽量避免使用它来进行对象和字节数组的转换。
分享:
扫描分享到社交APP
上一篇
下一篇