美文网首页
对象输入/输出流与序列化

对象输入/输出流与序列化

作者: 编程人生 | 来源:发表于2021-12-17 21:55 被阅读0次

            Java语言支持一种称为对象序列化(Object serialization) 的非常通用的机制,它可以将任何对象写出到输出流中,并在之后将其读回.

            保存和加载序列化对象

    看下面伪代码.

            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.dat"));

              Employee harry ....

              out.writeObject(harry);

            将对象读回代码如下:

            ObjectInputStream in  = new ObjectInputStream (new FileInputStream("employee.dat"));

            Employee e1 = (Employee)in.readObject();

    但是,对希望在对象输出流中存储或从对象输入流中恢复的所有类都应该修改一下, 这些类必须实现

    Serializable 接口.

            class Employee implemets Serializable

            {    

                .............................

             }

       Serializable 接口没有任何方法,因此你不需要对这些类做任何改动.

         只有在写出对象时才能有 writeObject/readObject 方法.  对于基本类型的值,你需要使用诸如writeInt/

    readInt  或 writeDouble / readDouble 这样的方法.

        为什么会称作对象序列化?而不叫做其他什么

        看下面这段代码:
        class Manager extends Employee

    {

            private Employee secretary;

            ......

    }

        在 Manager 对象里 有一个 secretary 对象.,而实际情况是两个不同的经理会用同一个秘书, 也就是说

    两个Manager 里都有 同一个秘书sercretary 对象.

    这种复杂的对象网路 ,我们不能保存和恢复秘书对象的内存地址,因为当对象重新加载时, 他可能占据的是与原来不同的内存地址.

        与此不同的是,每个对象都是一个序列号(serial number)保存的,这就是这种机制之所以称为序列化的原因.其算法是:

        1.对你遇到的每一个对象引用都关联一个序列号

         2.对于每个对象,当第一次遇到时,保存其对象数据到输出流中

         3.如果某个对象之前保存过,那么只写出"与之前保存过的序列号为x的对象相同"

    我们使用序列化将对象集合保存到磁盘文件中,并按照他们被存储的样子获取他们.

    序列化另一种非常重要的应用是通过网络将对象集合传送到另一台计算机上.因为序列化用序列号代替了内存地址,所以它允许将对象集合从一台机器传送到另一台机器.

           对于序列化 .我们应该记住:

            1.对象流输出中包含所有对象的类型和数据域

            2.每个对象都被赋予另一个序列号

            3.相同的对象的重复出现将被存储为这个对象的序列号的引用.

    在序列化对象时, 某些域不可以被序列化,例如,只对本地方法有意义的存储文件句柄或窗口句柄的整数值,这种信息在稍后重新加载对象或将其传送到其他机器上时都是没有用处.

    java 有一种简单的机制来预防这种域被序列化,那就是将它们用transient(瞬时的)标记.瞬时的域在被序列化时总会被跳过.

    例如: java.util.Date 类,它提供自己的readObject 和 writeObject 方法, 这些方法将日期写出从纪元(UTC

    时间1970年1月1日0点)开始的毫秒数,Date类有一个复杂的内部表示,为了优化查询, 它存储了一个Canlendar 对象和一个毫秒技术值, Canlendar 的状态是冗余的,因此并不需要保存.

    readObject 和 writeObject 方法 只需要保存和加载它们的数据域,而不需要关心超类数据和任何其他类的信息.

    序列化单例和类型安全的枚举

    序列化单例对象会破坏单例.,即使构造器是私有的,序列化机制也可以创建新的对象.

    为了解决这个问题,你需要定义另一种称为readResolve 的特殊序列化方法.如果定义了readResolve方法,在对象被序列化后就调用它.它必须返回一个对象,而该对象之后会称为readObject的返回值.

    版本管理

    我们一定见过下面这段代码

    static final long serialVersionUID = -18124134562313634315L;

    这句代码啥意思呢?

    我们如果序列化对象就会考虑程序演化时对象版本不一致问题.例如 1.1版本的可以读入旧文件吗?仍旧使用1.0版本的勇敢虎可以读入新写版本产生的文件吗?显然不同版本的文件都可以处理是我们想要的结果.

    上面那段代码就是我们解决问题的方法.只要加了那就句代码, 就可以解决不同版本文件问题.

    SHA指纹 听过没,  我们只要知道 当一个类发生变化时, 它的SHA指纹会跟着发生变化,而对象输入流将拒绝读入具有不同指纹的对象.但是,类可以表面它对早期版本保持兼容,想要这样做,就必须获得这个类的早期版本指纹.

    现在明白了, 我们最初版文件中加入上面那句代码, 我们就记住了版本的指纹,之后版本即使发生了变化,我们也可以正常用输入流读取旧版本文件.

    如果一个类具有名为serialVersionUID 的静态数据成员, 它就不再需要人工的计算其指纹,而只需直接使用这个值.

    一旦这个静态数据成员被置于某个类的内部, 那么序列化系统就可以读入这个类的对象的不同版本.

    如果这个类只是方法产生了变化, 那么读入新对象数据时是不会有任何问题的.但是,如果数据域发生变化,那么就可能有问题.

    为克隆使用序列化

    序列化有一个用法:提供了一种克隆对象的简便途径,只要对应类可序列化即可.做法很简单,直接将对象序列化到输出流中,然后将其读回.这样产生的新对象是对现有对象的一个深拷贝.在此过程中,我们不必将对象写出到文件中,因为可以用ByteArrayOutputStream将数据保存在字节数组中.

    package entity;

    import io.SerialCloneable;

    import java.io.Serializable;

    import java.time.LocalDate;

    public class Employeeextends SerialCloneable {

    private  Stringname;

    private  double salery ;

    private LocalDatehireDay;

    public void setName(String name) {

    this.name = name;

    }

    public void setSalery(int salery) {

    this.salery = salery;

    }

    public void setHireDay(LocalDate hireDay) {

    this.hireDay = hireDay;

    }

    public String getName() {

    return name;

    }

    public double getSalery() {

    return salery;

    }

    public LocalDate getHireDay() {

    return hireDay;

    }

    public Employee(String name,int salery ,int year ,int month,int day){

    this.name = name;

    this.salery = salery;

    this.hireDay = LocalDate.of(year,month,day);

    }

    /**

    * Raises the salary of this employee

        * @byPercent the percentage of hte raise

    */

        public void raiseSalary(double byPercent)

    {

    double raise =salery * byPercent/100;

    salery += raise;

    }

    @Override

        public String toString() {

    return "Employee{" +

    "name='" +name +'\'' +

    ", salery=" +salery +

    ", hireDay=" +hireDay +

    '}';

    }

    }

    package io;

    import java.io.*;

    public class SerialCloneableimplements  Cloneable , Serializable {

    public Object clone()throws  CloneNotSupportedException

    {

    //save the object to a bate array

            ByteArrayOutputStream baops =new ByteArrayOutputStream();

    try {

    try (ObjectOutputStream out =new ObjectOutputStream(baops)) {

    out.writeObject(this);

    }

    //read a clone of the object from the byte array

                try(InputStream bin =new ByteArrayInputStream(baops.toByteArray()))

    {

    ObjectInputStream in =new ObjectInputStream(bin);

    return in.readObject();

    }

    }catch (IOException | ClassNotFoundException e){

    CloneNotSupportedException e2 =new CloneNotSupportedException();

    e2.initCause(e);

    throw e2;

    }

    }

    }

    package Test;

    import entity.Employee;

    public class SerialCloneTest {

    public static void main(String[] args)throws CloneNotSupportedException{

    Employee harry =new Employee("Harry Hanker",25000,1989,10,1);

    //Clone harry

            Employee harry2 = (Employee) harry.clone();

    //mutate harry

            harry.raiseSalary(10);

    System.out.println(harry);

    System.out.println(harry2);

    System.out.println(harry == harry2);

    }

    }

    相关文章

      网友评论

          本文标题:对象输入/输出流与序列化

          本文链接:https://www.haomeiwen.com/subject/whlbfrtx.html