IO流之序列化与反序列化技术、局部序列化技术

一、概述

  • 序列化
    • 将程序中的对象直接以文件的形式存储,是按照对象在内存中的序列进行存储的
  • 反序列化
    • 从文件中把对象数据重新读到程序中
      • 就是把之前序列化的对象从文件里读出,重新变成对象
  • 2018年java官方发现程序中至少有1/3的bug是因为使用了序列化和反序列化技术产生的,这种序列化安全问题如果不能有更合适的技术替代,短期内是不会剔除序列化技术的,否则会影响整个java生态

二、实现序列化

1、Serializable接口实现序列化

  • 想要实现序列化的类,需要实现Serializable接口,使用到对象输出流ObjectOutputStream
  • 这是一个标记接口
    • 底层源码中,ObjectOutputStream会判断写入的对象是否为字符串,或者使用instanceof判断是否是Serializable或其实现类,如果不满足这些条件会抛出异常
  • 使用ObjectOutputStream需要传入一个OutputStream对象
  • 实现Serializable接口的类如果想要序列化,所有属性也必须实现Serializable接口,否则无法序列化

2、实现部分属性序列化的四种方式

  • 使用transient修饰属性
    • 前提是要该类实现Serializable接口
    • 被transient修饰的属性不会被序列化处理
  • 使用static修饰符
    • 前提是要该类实现Serializable接口
    • 被static修饰的属性不会被序列化处理
  • 使用默认方法writeObject和readObject
    • 在实现Serializable接口的类中定义这两个方法
    • 这两个方法必须都是private void修饰,否则不生效
    • writeObject方法用于自定义序列化的内容和顺序
    • readObject方法用于自定义反序列化的内容和顺序,需要和writeObject方法的内容顺序一致

  • 实现Externalizable接口
    • Externalizable接口继承自Serializable接口
    • 需要重写两个方法
      • writeExternal
        • 入参ObjectOutput out
        • 使用out.writeObject(属性名)指定需要序列化的属性
      • readExternal
        • 入参ObjectInput in
        • 使用in.readObject()获取反序列化后的属性值,需要与序列化属性的顺序一致

3、举例

a、验证transient和static修饰的属性无法被序列化

验证transient和static修饰的属性无法被序列化

  • 定义Person类,实现Serializable接口。其中
    • a属性使用transient修饰(无法被序列化),b属性使用static修饰(无法被序列化),c属性正常定义(可以被序列化)
package SerializableTest;

import java.io.Serializable;

public class Person implements Serializable {
    //使用transient修饰无法被序列化
    private transient String a;
    private static String b;
    private String c;

    @Override
    public String toString() {
        return "Person{" +
                "a='" + a + '\'' +
                ", b='" + b + '\'' +
                ", c='" + c + '\'' +
                '}';
    }

    public String getA() {
        return a;
    }

    public void setA(String a) {
        this.a = a;
    }

    public static String getB() {
        return b;
    }

    public static void setB(String b) {
        Person.b = b;
    }

    public String getC() {
        return c;
    }

    public void setC(String c) {
        this.c = c;
    }
}
  • 定义序列化工具类
package SerializableTest;

import java.io.*;

public class SerializableUtils {
    //序列化操作
    public static void mySerializable(Object obj,String fileName) throws IOException {
        FileOutputStream fos = new FileOutputStream(fileName);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
        oos.close();
        fos.close();
    }

    //反序列化操作
    public static Object myDesSerializable(String fileName) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(fileName);
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object o = ois.readObject();
        return o;
    }
}
  • 定义测试类
package SerializableTest;

import java.io.IOException;

public class test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person p = new Person();
        p.setA("A");
        Person.setB("B");
        p.setC("C");
        System.out.println("序列化之前的p对象:"+p); //Person{a='A', b='B', c='C'}
        //序列化操作
        SerializableUtils.mySerializable(p,"I:\\a.txt");
        //修改b属性的值,不会影响到a.txt文件中的b属性值
        Person.setB("bbbb");
        //反序列化操作
        Object o = SerializableUtils.myDesSerializable("I:\\a.txt");
        if (o instanceof Person){
            p = (Person) o;
        }
        //b是static修饰的,如果也能序列化,结果应该还是B,但是这里的结果是bbb,说明static修饰不可被序列化
        //a为null,说明在序列化操作的时候a属性并没有被序列化
        System.out.println("序列化后的p对象:"+p); //Person{a='null', b='bbbb', c='C'}
    }
}
  • 经过测试发现,transient和static修饰的属性确实无法被实例化

b、验证使用默认方法writeObject和readObject指定序列化的属性

验证使用默认方法writeObject和readObject指定序列化的属性

  • 实现Serializable接口默认会
  • 重新编写Person类,令a和b进行序列化,c不进行序列化
package SerializableTest;

import java.io.IOException;
import java.io.Serializable;

public class Person implements Serializable {
    //使用transient修饰无法被序列化
    private String a;
    private String b;
    private String c;

    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        //序列化a和b属性,不序列化c属性
        System.out.println("writeObject进行序列化处理...");
        out.writeObject(a);
        out.writeObject(b);
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        //反序列化读取a和b属性的值
        System.out.println("readObject进行反序列化处理...");
        a = (String) in.readObject();
        b = (String) in.readObject();
    }

    @Override
    public String toString() {
        return "Person{" +
                "a='" + a + '\'' +
                ", b='" + b + '\'' +
                ", c='" + c + '\'' +
                '}';
    }

    public String getA() {
        return a;
    }

    public void setA(String a) {
        this.a = a;
    }

    public String getB() {
        return b;
    }

    public void setB(String b) {
        this.b = b;
    }

    public String getC() {
        return c;
    }

    public void setC(String c) {
        this.c = c;
    }
}
  • 工具类不变
  • 测试类
package SerializableTest;

import java.io.IOException;

public class test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person p = new Person();
        p.setA("A");
        p.setB("B");
        p.setC("C");
        System.out.println("序列化之前的p对象:"+p); //Person{a='A', b='B', c='C'}
        //序列化操作
        SerializableUtils.mySerializable(p,"I:\\a.txt");
        //修改b属性的值,不会影响到a.txt文件中的b属性值
        p.setB("bbbb");
        //反序列化操作
        Object o = SerializableUtils.myDesSerializable("I:\\a.txt");
        if (o instanceof Person){
            p = (Person) o;
        }
        //b是static修饰的,如果也能序列化,结果应该还是B,但是这里的结果是bbb,说明static修饰不可被序列化
        //a为null,说明在序列化操作的时候a属性并没有被序列化
        System.out.println("序列化后的p对象:"+p); //Person{a='null', b='bbbb', c='C'}
    }
}
  • 测试结果
    • 可见c确实不能被序列化


c、验证实现Externalizable接口序列化部分属性

验证实现Externalizable接口序列化部分属性

  •  修改Person类,实现Externalizable接口,并重写两个方法
package SerializableTest;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Person implements Externalizable {
    //使用transient修饰无法被序列化
    private String a;
    private String b;
    private String c;

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("writeExternal进行序列化操作...");
        out.writeObject(a);
        out.writeObject(b);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println("readExternal进行反序列化操作...");
        a = (String) in.readObject();
        b = (String) in.readObject();
    }

    @Override
    public String toString() {
        return "Person{" +
                "a='" + a + '\'' +
                ", b='" + b + '\'' +
                ", c='" + c + '\'' +
                '}';
    }

    public String getA() {
        return a;
    }

    public void setA(String a) {
        this.a = a;
    }

    public String getB() {
        return b;
    }

    public void setB(String b) {
        this.b = b;
    }

    public String getC() {
        return c;
    }

    public void setC(String c) {
        this.c = c;
    }
}
  • 工具类保持不变
  • 测试类
package SerializableTest;

import java.io.IOException;

public class test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person p = new Person();
        p.setA("A");
        p.setB("B");
        p.setC("C");
        System.out.println("序列化之前的p对象:"+p); //Person{a='A', b='B', c='C'}
        //序列化操作
        SerializableUtils.mySerializable(p,"I:\\a.txt");
        //修改b属性的值,不会影响到a.txt文件中的b属性值
        p.setB("bbbb");
        //反序列化操作
        Object o = SerializableUtils.myDesSerializable("I:\\a.txt");
        if (o instanceof Person){
            p = (Person) o;
        }
        //b是static修饰的,如果也能序列化,结果应该还是B,但是这里的结果是bbb,说明static修饰不可被序列化
        //a为null,说明在序列化操作的时候a属性并没有被序列化
        System.out.println("序列化后的p对象:"+p); //Person{a='null', b='bbbb', c='C'}
    }
}
  • 测试结果
    • c未被序列化,说明实现Externalizable接口也能实现序列化部分属性

 d、Serializable接口与Externalizable接口的区别

区别 Serializable Externalizable
实现复杂度 实现简单,java对其有内建支持 实现复杂,由开发人员自己完成
执行效率 所有属性由java统一保存,性能较低 开发人员决定对象保存哪些属性,可以提高执行效率
占用空间 保存数据占用空间大 部分存储,占用空间相对较小
使用频率 偏低

三、实现反序列化

  • 将之前序列化存储的对象从文件中读出,重新变成程序中的对象
  • 使用了ObjectInputStream
    • 需要传入一个InputStream对象
  • 举例见实现序列化的例子

猜你喜欢

转载自blog.csdn.net/future_god_qr/article/details/121210500