谈谈你对序列化的理解?

1 什么是序列化和反序列化?

Java序列化是指把Java对象转换为字节序列的过程;Java反序列化是指把字节序列恢复为Java对象的过程。

2 为什么要序列化?

其实我们的对象不只是存储在内存中,它还需要在传输网络中进行传输,并且保存起来之后下次再加载出来,这时候就需要序列化技术。

一般Java对象的生命周期比Java虚拟机短,而实际开发中如果需要JVM停止后能够继续持有对象,则需要用到序列化技术将对象持久化到磁盘或数据库。

在多个项目进行RPC调用时,需要在网络上传输JavaBean对象,而网络上只允许二进制形式的数据进行传输,这时则需要用到序列化技术。

Java的序列化技术就是把对象转换成一串由二进制字节组成的数组,然后将这二进制数据保存在磁盘或传输网络。而后需要用到这对象时,磁盘或者网络接收者可以通过反序列化得到此对象,达到对象持久化的目的。

3序列化原理?

Serializable类的源码

public interface Serializable {
}

可以看到该类的内部实现完全为空,在Java IO体系中仅起一个标记的作用:
首先定义一个User对象:

import java.io.Serializable;

public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String id;
    private String name;

    public User(String id, String name) {
        this.id = id;
        this.name = name;
    }
}

接着我们定义一个类来读写这个User类的对象:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializableTest {
    private static void write() {
        User user = new User("1001", "Bob");
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("E:\\documents\\java\\leetcode\\src\\user.txt"));
            outputStream.writeObject(user);
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        write();
    }
}

运行上述代码后,可以查看user.txt文件,其中的数据是以二进制的形式:

�� {sr  java.io.NotSerializableException(Vx5  xr java.io.ObjectStreamExceptiond��k�9��  xr java.io.IOExceptionl�sde%�  xr java.lang.Exception��>;�  xr java.lang.Throwable��5'9w�� L causet Ljava/lang/Throwable;L 
detailMessaget Ljava/lang/String;[ 

此时User对象已经被持久化到文件中,接着我们将User实现Serializable接口的代码去掉:

java.io.NotSerializableException: thinking.in.java.common.User
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at thinking.in.java.SerializableTest.write(SerializableTest.java:13)
    at thinking.in.java.SerializableTest.main(SerializableTest.java:30)

抛出了以上异常,提示不可序列化的异常,然后我们到ObjectOutputStream类中的1184行看一下,这一部分的代码是这样的:

if (obj instanceof String) {
    writeString((String) obj, unshared);
} else if (cl.isArray()) {
    writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
    writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
    writeOrdinaryObject(obj, desc, unshared);
} else {
    if (extendedDebugInfo) {
        throw new NotSerializableException(
            cl.getName() + "\n" + debugInfoStack.toString());
    } else {
        throw new NotSerializableException(cl.getName());
    }
}

如上所示,在else if中通过判断obj instanceof Serializable,如果对象没有实现序列化接口,就无法序列化。可以想见,Java中的每一处序列化都进行了类似的检查,也就是说,没有实现Serializable接口的对象是无法通过IO操作持久化。

public class SerializableTest {

    private static void read() {
        try {
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("Z:\\workspace\\practice\\user.txt"));
            User user = (User) inputStream.readObject();
            System.out.println(user);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        read();
    }
}
thinking.in.java.common.User@58372a00

此时如果将User实现Serializable接口的代码部分去掉,发现也无法将文本转换为序列化对象,反序列化异常:

java.io.InvalidClassException: thinking.in.java.common.User; class invalid for deserialization
    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:169)
    at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:874)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2043)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
    at thinking.in.java.SerializableTest.read(SerializableTest.java:23)
    at thinking.in.java.SerializableTest.main(SerializableTest.java:30)
SerialVersionUID

对于JVM来说,要进行持久化的类必须要有一个标记,只有持有这个标记JVM才允许类创建的对象可以通过其IO系统转换为字节数据,从而实现持久化,而这个标记就是Serializable接口。而在反序列化的过程中则需要使用serialVersionUID来确定由那个类来加载这个对象,所以我们在实现Serializable接口的时候,一般还会要去尽量显示地定义serialVersionUID,如:

private static final long serialVersionUID = 1L;

在反序列化的过程中,如果接收方为对象加载了一个类,如果该对象的serialVersionUID与对应持久化时的类不同,那么反序列化的过程中将会导致InvalidClassException异常。例如,在之前反序列化的例子中,我们故意将User类的serialVersionUID改为2L,如:

private static final long serialVersionUID = 2L;
java.io.InvalidClassException: thinking.in.java.common.User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
    at thinking.in.java.SerializableTest.read(SerializableTest.java:23)
    at thinking.in.java.SerializableTest.main(SerializableTest.java:30)

如果我们在序列化中没有显示地声明serialVersionUID,则序列化运行时将会根据该类的各个方面计算该类默认的serialVersionUID值。但是,Java官方强烈建议所有要序列化的类都显示地声明serialVersionUID字段,因为如果高度依赖于JVM默认生成serialVersionUID,可能会导致其与编译器的实现细节耦合,这样可能会导致在反序列化的过程中发生意外的InvalidClassException异常。因此,为了保证跨不同Java编译器实现的serialVersionUID值的一致,实现Serializable接口的必须显示地声明serialVersionUID字段。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值