【Java基础】序列化和反序列化

序列化概念

  1. 序列化是指把一个Java对象变成二进制内容,本质上就是把java对象转换成一个byte[]数组

  2. 为什么要把Java对象序列化呢?

    • 因为序列化后可以把byte[]数组保存到文件(磁盘)中,或者把byte[]数组通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了。
  3. 反序列化,即把一个二进制内容(也就是byte[]数组)变回Java对象。有了反序列化,保存到文件中的byte[]数组又可以“变回”Java对象“,或者从网络上读取byte[]并把它“变回”Java对象"

序列化注意事项

序列化能保存的元素
  1. 只能保存对象的非静态成员变量

  2. 不能保存任何成员方法和静态的成员变量

  3. 不保存transient成员变量

  4. 如果一个对象的成员变量是一个引用类型的变量,这个对象的成员变量也会保存

  5. 序列化保存的只是变量的值,对于变量的任何修饰符,都不能保存

  6. 如果一个可序列化的对象包含对某个不可序列化的对象成员变量,那么整个序列化操 作将会失败,并且会抛出一个NotSerializableException。如果我们将这个成员变量设置为transient,那么对象仍然可以序列化。

同一个对象多次序列化的处理
  • 所有保存到磁盘中的对象都有一个序列化编号
  • 序列化一个对象中,首先检查该对象是否已经序列化过
  • 如果没有,进行序列化 ,如果已经序列化,将不再重新序列化,而是输出编号即可
敏感属性禁止序列化

如果不希望某些属性(敏感)序列化,或不希望出现递归序列

  1. 为属性添加transient关键字(完成排除在序列化之外)
  2. 自定义序列化(不仅可以决定哪些成员变量不参与序列化,还可以定义成员变量具体如何序列化
序列化版本不兼容
  • 修改了引用类型成员变量后,会影响版本号,从而导致反序列化不成
    解决方案:为Java对象指定序列化版本号serialVersionUID

对象序列化的条件

  1. 只有实现了Serializable接口的类的对象才可以被序列化。
    Serializable接口中没有任何的方法,称为标记接口,用于,标记当前类是否可以序列化。

  2. 如果对象的成员是对象,成员变量对应类也必须实现Serializable接口

序列化

将Java对象转换成字节序列(IO字节流) ,简单来说就是:使用ObjectOutputStream,把一个Java对象变为byte[]数组

    public static void main(String[] args) throws IOException {
        
        try (
        ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 
       ObjectOutputStream output = new ObjectOutputStream(buffer)
        ) {
            // 写入int:
            output.writeInt(12345);
            // 写入String:
            output.writeUTF("Hello");
            // 写入Object  output.writeObject(Double.valueOf(123.456));

 //获取写入字节                System.out.println(Arrays.toString(buffer.toByteArray()));
    }    
  }

ObjectOutputStream既可以写入基本类型,如int,boolean,也可以写入String(以UTF-8编码),还可以写入实现了Serializable接口的Object。
因为写入Object时需要大量的类型信息,所以写入的内容很大。

反序列化

从字节序列中恢复Java对象,简单来说就是: 使用ObjectInputStream把一个byte[]数组变成Java对象

try (ObjectInputStream input = new ObjectInputStream("text.dat")) {
    int n = input.readInt();
    String s = input.readUTF();
    Double d = (Double) input.readObject();
}

除了能读取基本类型String类型外,调用readObject()可以直接返回一个Object对象。要把它变成一个特定类型,必须强制转型

readObject()可能抛出的异常有:

  • ClassNotFoundException:没有找到对应的Class;
    ,例如,Person对象序列化以后,通过网络传给另一台电脑上的另一个Java程序,但是这台电脑的Java程序并没有定义Person类,所以无法反序列化。
  • InvalidClassException:Class不匹配。
    • 对于InvalidClassException,这种情况常见于序列化的Person对象定义了一个int类型的age字段,但是反序列化时,Person类定义的age字段被改成了long类型,所以导致class不兼容。
    • 为了避免这种class定义变动导致的不兼容,Java的序列化允许class定义一个特殊的serialVersionUID静态变量用于标识Java类的序列化“版本”通常可以由IDE自动生成。如果增加或修改了字段,可以改变serialVersionUID的值,这样就能自动阻止不匹配的class版本:
    public class Person implements Serializable {
     private static final long serialVersionUID = 2709425275741743919L;
    }
    

要特别注意反序列化的几个重要特点:

  • 反序列化时,由JVM直接构造出Java对象,不调用构造方法构造方法内部的代码,在反序列化时根本不可能执行。

  • 因为Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。 一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。

实际上,Java本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。 更好的序列化方法是通过JSON这样的通用数据结构来实现,只输出基本类型(包括String)的内容,而不存储任何与代码相关的信息。

小结

  1. 要序列化的Java对象必须实现java.io.Serializable接口,类似Serializable这样的空接口被称为“标记接口”(Marker Interface)

  2. 反序列化时不调用构造方法,可设置serialVersionUID作为版本号(非必需)

  3. Java的序列化机制仅适用于Java,如果需要与其它语言交换数据,必须使用通用的序列化方法,例如JSON。

https://www.cnblogs.com/niceyoo/p/10596657.html
https://www.jianshu.com/p/89c2a19772e2

发布了62 篇原创文章 · 获赞 109 · 访问量 5282

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/103759601