java序列化和反序列化

简介: java序列化和反序列化

引语:

    平时我们在运行程序的时候,创建的对象都在内存中,当程序停止或者中断了,对象也就不复存在了.如果我们能将对象保存起来,在需要使用它的时候在拿出来使用就好了,并且对象的信息要和我们保存
时的信息一致.序列化就可以解决了这样的问题.序列化当然不止一种方式,如下:

序列类型 是否跨语言 优缺点
hession 支持 跨语言,序列化后体积小,速度较快
protostuff 支持 跨语言,序列化后体积小,速度快,但是需要Schema,可以动态生成
jackson 支持 跨语言,序列化后体积小,速度较快,且具有不确定性
fastjson 支持 跨语言支持较困难,序列化后体积小,速度较快,只支持java,c#
kryo 支持 跨语言支持较困难,序列化后体积小,速度较快
fst 不支持 跨语言支持较困难,序列化后体积小,速度较快,兼容jdk
jdk 不支持 序列化后体积很大,速度快

我们今天介绍的就是java原生的Serializable序列化.
先列一下概念:
序列化:序列化是将对象的状态信息转换为可以存储或传输的形式的过程
反序列化:从存储或传输形式还原为对象

Serializable的使用

序列化使用起来很简单只需要实现Serializable接口即可,然后序列化(序列化是将对象的状态信息转换为可以存储或传输的形式的过程)和反序列化(反之,从存储或传输形式还原为对象).
只要使用ObjectOutputStream和ObjectInputStream将对象转为二进制序列和还原为java对象.话不多说,看下代码示例:

private static void testSerializable(String fileName) throws IOException {
       try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(fileName))) {
           // "XXX" 的String也可以直接作为对象进行反序列化的
           objectOutputStream.writeObject("test serializable");
           SerializableData data = new SerializableData(1, "testStr");
           objectOutputStream.writeObject(data);
       }
   }

   private static void testDeserializable(String fileName) throws IOException, ClassNotFoundException {
       try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(fileName))) {
           String str = (String) objectInputStream.readObject();
           System.out.println("String的反序列化: " + str);
           SerializableData readData = (SerializableData) objectInputStream.readObject();
           System.out.println("反序列化的对象: " + readData.toString());
           // 输出:反序列化的对象: SerializableData(testInt=1, testStr=testStr)
       }
   }

// 使用到的类
@Data
@AllArgsConstructor
class SerializableData implements Serializable {

    private Integer testInt;

    private String testStr;
}

第一个方法是传入文件路径,将String和SerializableData对象序列化到fileName指定的文件中;第二个方法是反序列化将文件中的二进制还原为java对象.
这里其实比较简单没有什么大问题,稍微提一句的就是writeObject这个方法是可以直接将"写入的字符串"这种形式的对象直接序列化为二进制的.
这里还有一点就是反序列化的版本号必须和原本对象的版本号(private static final long serialVersionUID = 1L;这个因为是自己测试所以没有写默认是1L,修改后,反序列化的对象版本号不一致会报错)一致,并且jvm能找到反序列化的文件的位置,否则都会失败.

transient关键字

简单的使用序列化和反序列化应该没有什么问题,我们再来看看transient关键字是啥?在某些场景下,我们需要写入或者还原的数据中其实有我们不需要透露或者说不想暴露给外部的数据,如果我们将这些隐私的数据序列化,在反序列化出来,
那么这些信息就泄漏了.而transient关键字呢,就是防止这种事情的发生.
当属性被加上了transient关键字以后,序列化时不会将该属性的值给写入,所以反序列化的时候我们会发现原本写入的数据,还原出来是null.
我们写一个例子看看是否是这样呢?

private static void testTransient() throws IOException, ClassNotFoundException {
       String fileName = "transientData.txt";
       try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName))) {
           Account data = new Account(1, "user1", "123456");
           out.writeObject(data);
       }
       try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(fileName))) {
           Account readData = (Account) in.readObject();
           System.out.println("transient关键字的对象: " + readData.toString());
           // 输出: transient关键字的对象: Account(id=1, userName=user1, idCardNumber=null)
       }
   }

// 对应的对象
@Data
@AllArgsConstructor
class Account implements Serializable {

    private Integer id;

    private String userName;

    private transient String idCardNumber;
}

这里我们有一个Account对象,我们不想暴露出我们的省份证号码idCardNumber,于是乎加上了transient关键字.
然后将idCardNumber已经初始化过的data对象序列化,当我们再反序列化去取得这个idCardNumber的值的时候,发现确实对象的idCardNumber是null,transient是起作用的.
如果是对基本类型数据加上transitent话,会得到对应的默认值,就好比是int的数据类型,得到的就是0.

Externalizable的使用

使用过自动的序列化和反序列化以后,我们又想在序列化和反序列化的时候我们能不能自己控制呢?在序列化和反序列化的时候我们能不能加点日志或者其他的操作之类的呢?
是的,阔以的.只需要轻轻一点,实现Externalizable接口即可,和Serializable使用差不多.

private static void testExternalizable() throws IOException, ClassNotFoundException {
        String fileName = "testExternalizable.txt";
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName))) {
            Account2 data = new Account2("user1", 1);
            out.writeObject(data);
        }
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(fileName))) {
            Account2 readData = (Account2) in.readObject();
            System.out.println("Externalizable的对象: " + readData.toString());
        }
    }

// 使用到的对象
@Data
@AllArgsConstructor
class Account2 implements Externalizable {

    private Integer id;

    private String userName;

    private transient String idCardNumber;

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("执行了writeExternal方法");
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println("执行了readExternal方法");
    }
}

如果执行了上面的代码,恭喜你,获得一个Exception的奖励.大概长这样,java.io.InvalidClassException:XXX no valid constructor,
Externalizable在执行的时候会调用默认的无参构造函数,而且记住哦,必须是public的,如果没有加public你会发现又奖励了一个Exception给你.
讲道理这个是比较坑的.下面我们来看看正确的用法,序列化和反序列化都是我们自己控制的:

private static void testExternalizable() throws IOException, ClassNotFoundException {
       String fileName = "testExternalizable.txt";
       try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName))) {
           Account3 data = new Account3("user1", 1);
           out.writeObject(data);
       }
       try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(fileName))) {
           Account3 readData = (Account3) in.readObject();
           System.out.println("Externalizable的对象: " + readData.toString());
           /**
            * 输出:
            * 执行了writeExternal方法
            * 执行了readExternal方法
            * Externalizable的对象: Account3(userName=user1, id=1)
            */
       }
   }

@ToString
class Account3 implements Externalizable {

    private String userName;

    private Integer id;

    public Account3() {

    }

    public Account3(String userName, Integer id) {
        this.userName = userName;
        this.id = id;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("执行了writeExternal方法");
        out.writeObject(userName);
        out.writeInt(id);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println("执行了readExternal方法");
        userName = (String) in.readObject();
        id = in.readInt();

    }
}

总结:

1.我们介绍了jdk自带的序列化和反序列化(和其中的一些坑点);
2.知道了transient可以将隐私数据不序列化;
3.还有Externalizable可以自己来控制序列化和反序列化的进程.

参考资料:

https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html#a6428(官网)

相关文章
|
1天前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。
|
7天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
33 5
|
8天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
17 3
|
11天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
25天前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
1月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第3天】在Java编程的世界里,对象序列化与反序列化是实现数据持久化和网络传输的关键技术。本文将深入探讨Java序列化的原理、应用场景以及如何通过代码示例实现对象的序列化与反序列化过程。从基础概念到实践操作,我们将一步步揭示这一技术的魅力所在。
|
30天前
|
消息中间件 存储 Java
大数据-58 Kafka 高级特性 消息发送02-自定义序列化器、自定义分区器 Java代码实现
大数据-58 Kafka 高级特性 消息发送02-自定义序列化器、自定义分区器 Java代码实现
41 3
|
11天前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
8 0
|
1月前
|
安全 网络协议 Java
Java反序列化漏洞与URLDNS利用链分析
Java反序列化漏洞与URLDNS利用链分析
45 3
|
存储 缓存 安全
Java安全之反序列化漏洞分析
Java安全之反序列化漏洞分析
425 0
Java安全之反序列化漏洞分析