序列化

100 阅读6分钟

序列化从头说:

 在面向对象程序设计中,类是个很重要的概念。所谓“类”,可以将它想像成建筑图纸,而对象就是根据图纸盖的大楼。类,规定了对象的一切。根据建筑图纸造房子,盖出来的就是大楼,等同于将类进行实例化,得到的就是对象。一开始,在源代码里,类的定义是明确的,但对象的行为有些地方是明确的,有些地方是不明确的。对象里不明确地方,是因为对象在运行的时候,需要处理无法预测的事情,诸如用户点了下屏幕,用户点了下按钮,输入点东西,或者需要从网络发送接收数据之类的。后来,引入了泛型的概念之后,类也开始不明确了,如果使用了泛型,直到程序运行的时候,才知道究竟是哪种对象需要处理。对象可以很复杂,也可以跟时序相关。一般来说,“活的”对象只生存在内存里,关机断电就没有了。一般来说,“活的”对象只能由本地的进程使用,不能被发送到网络上的另外一台计算机。序列化,可以存储“活的”对象,可以将“活的”对象发送到远程计算机。把“活的”对象序列化,就是把“活的”对象转化成一串字节,而“反序列化”,就是从一串字节里解析出“活的”对象。于是,如果想把“活的”对象存储到文件,存储这串字节即可,如果想把“活的”对象发送到远程主机,发送这串字节即可,需要对象的时候,做一下反序列化,就能将对象“复活”了。将对象序列化存储到文件,术语又叫“持久化”。将对象序列化发送到远程计算机,术语又叫“数据通信”。ava对序列化提供了非常方便的支持,在定义类的时候,如果想让对象可以被序列化,只要在类的定义上加上了”implements Serializable”即可,比如说,可以这么定义”public class Building implements Serializable”,其他什么都不要做,Java会自动的处理相关一切。Java的序列化机制相当复杂,能处理各种对象关系。Java的序列化机制的缺点就是计算量开销大,且序列化的结果体积大太,有时能达到对象大小的数倍乃至十倍。它的引用机制也会导致大文件不能分割的问题。

3.1 概述

——————————————————————————

java提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据,对象的类型和对象中存储的属性等信息。字节序列写出文件之后,相当于文件中初九保存了一个对象信息。

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据,对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象,看图理解序列化:

3.2 ObjectOutputStream类

——————————————————————————

java.io.ObjectOutputStream类,将java对象的原始数据类型写出到文件,实现对象的持久化存储。

构造方法

  • public ObjectOutputStream(OutputStream out):创建一个指定的OutputStream的ObjectOutputStream

构造举例,代码如下

FileOutputStream fileOut=new FileOutputStream("employee.txt);

ObjectOutputStream out=new ObjectOutputStream(fileOut);

序列化操作

  1. 一个对象要想序列化,必须满足两个条件:
  • 该类必须实现java.io.Serializable接口,Serializable是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException。
  • 该类的所有属性必须是可序列化的,如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient关键字修饰。
public class SerializeableDemo {
    public static void main(String[] args) {
        Employee e = new Employee("张三", "北京路", 20);
        //创建序列化流对象
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:\\01-尚硅谷MySQL核心技术-婷姐\\资料、代码\\note\\a.txt"));
        ) {
            //写出对象
            out.writeObject(e);
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        } catch (IOException e1) {
            e1.printStackTrace();
        }

    }
}

class Employee implements Serializable {
    public String name;
    public String address;
    public transient int age;//transient瞬态修饰成员,不会被序列化


    public Employee(String name, String address, int age) {
        this.name = name;
        this.address = address;
        this.age = age;
    }

    public void addressCheck() {
        System.out.println("Address check:" + name + "--" + address);
    }

3.3 ObjectInputStream类

——————————————————————————

ObjectInputStream反序列化流,将之前使用的ObjectOutputStream序列化的原始数据恢复为对象。

构造方法

  • public ObjectInputStream(InputStream in):创建一个指定的InputStream的ObjectInputStream

反序列化操作1

如果能够找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream读取对象的方法:

  • public final Object readObject():读取一个对象。
public class DeserializeDemo {
    public static void main(String[] args) {

        Employee emp = null;

        //创建反序列化的流
        try (  FileInputStream fileIn = new FileInputStream("E:\\01-尚硅谷MySQL核心技术-婷姐\\资料、代码\\note\\a.txt");
               ObjectInputStream in = new ObjectInputStream(fileIn);) {

            emp = (Employee) in.readObject();



        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }


        System.out.println("name:" + emp.name);
        System.out.println("address" + emp.address);
        System.out.println("age" + emp.age);

    }
}

对于JVM可以反序列化对象,它必须是能够找到class文件的二类。如果找不到该类的class文件,则抛出一个ClassNotFoundException异常。

反序列化操作2 另外,当JVM反序列化对象的时候,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:

  • 该类的序列版本号与从流中读取的类描述符的版本号不匹配
  • 该类包含未知数据类型
  • 该类没有可访问的无参构造方法

Serializable接口给需要需要序列化的类,提供了一个序列版本号。serialVersionUID该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

class Employee implements Serializable {

    //加入序列版本号
    private static final long serialVersionUID = 1;
    
    public String name;
    public String address;
    public transient int age;//transient瞬态修饰成员,不会被序列化


    public Employee(String name, String address, int age) {
        this.name = name;
        this.address = address;
        this.age = age;
    }

    public void addressCheck() {
        System.out.println("Address check:" + name + "--" + address);
    }
}

3.4 练习:序列化集合

——————————————————————————

  1. 将存有多个自定义对象的集合序列化操作,保存到list.txt文件中。
  2. 反序列化list.txt,并遍历集合,打印对象信息。

案例分析

  1. 把若干学生对象,保存到集合中。
  2. 把集合序列化。
  3. 反序列化的时候,只需要读取一次,转换为集合类型
  4. 遍历集合,可以打印所有学生的信息
public class SerTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

        //创建学生对象
        Student student1 = new Student("老王");
        Student student2 = new Student("老杭");
        Student student3 = new Student("老李");

        ArrayList<Student> arrayList = new ArrayList<>();
        arrayList.add(student1);
        arrayList.add(student2);
        arrayList.add(student3);

        //序列化操作
        serializ(arrayList);


        //反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
        //读取对象,强转为ArrayList类型
        ArrayList<Student> list = (ArrayList<Student>) ois.readObject();

        for (Student s : list) {
            System.out.println(s.getName());        }


    }


    public static void serializ(ArrayList<Student> arrayList) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
        ) {
            //写出对象
            oos.writeObject(arrayList);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


class Student implements Serializable {

    private final long SerialVersionUID = 1;

    private String name;



    public Student(String name) {
        this.name = name;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}