zoukankan      html  css  js  c++  java
  • Java I/O (2)

    先说概念:

    一、相关概念

    序列化:把内存中的java对象转换成与平台无关的二进制字节序列,以便永久保存在磁盘上或通过网络进行传输。序列化是Java提供的一种将对象写入到输出流、并在之后将其读回的机制。

    Java提供的对对象进行读写的流对象(即承载对象的媒介)为ObjectOutputStream 和 ObjectInputStream ,它们的作用就是把对象转换为字节序列,并承载这些序列,并提供了writeObject() 和 readObject() 方法对这些字节序列进行读写。

    那么,如何实现对象的序列化与反序列化呢?

    二、序列化、反序列化的方法

    方法(一)-- Serializable接口

    2.1 使用Serializable接口的默认序列化机制

    1、实现Serializable接口

    如果某个类对象要被序列化,该类必须实现Serializable接口,只接口内无具体方法,所以只需要声明实现该接口,而无需实现具体方法。

    public class Student implements Serializable { 
        …… }

    2、通过ObjectOutputStream 类对象的writeObject(obj)方法,将obj参数指定的对象进行序列化,把得到的字节序列写到目标输出流中。

            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("student.dat"));
            Student s = new Student();
            out.writeObject(s);
            out.close();

    3、通过ObjInputStream类对象的readObject()方法从源输入流中读取字节序列,再反序列化为对象,并将其返回。

            //反序列化得到对象
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("student.dat"));
            Student s1 = (Student) in.readObject();
            in.close();

     

    以上是简单的对象序列化,使用默认序列化机制,会将对象的全部域都进行序列化,同时对该对象引用的其他对象也进行序列化,同样的,对这些引用对象进行序列化的时候如果有另外的引用,这些引用也会被序列化,以此类推。如果一个对象包含的成员变量是容器对象,容器的元素还是容器,那么序列化过程会变得十分繁琐。另外,有时候对象的一些数据域只在本地有意义,对这样的数据进行序列化后存储或传输没有意义,甚至有时会在反序列化后使用对象时引起崩溃。所以,我们需要指定一些字段不需要序列化。

    2.2 自定义序列化

    序列化的实际应用中通常需要忽略某些数据域或简化序列化过程,此时就需要自定义序列化的过程,自定义序列化的方法有如下几种:1、使用transient关键字;2、使用writeObject()和readObject()方法  3、使用External接口实现序列化。 

    1、使用transient关键字

    在对未来会进行序列化的类进行定义时,使用transient关键字声明无需序列化的字段。默认序列化机制会忽略被transient修饰的字段。

    2、使用writeObject()与readObject()

    在可序列化的类中添加私有的writeObject和readObject方法,定义这个两个方法后,不再使用默认的序列化机制,而是执行这两个方法中的内容。在这个方法中可以向默认的读写行为中添加验证或任何其他行为。

        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
            in.defaultReadObject();//调用默认序列化机制
            //添加逻辑
        }
        
        private void writeObject(ObjectOutputStream out) throws IOException {
            out.defaultWriteObject();//调用默认序列化机制
            //添加逻辑  
        }

    使用这种方法可以序列化某些不可被序列化的域。有类似这样的情形,某可序列化的类中一个域是某不可序列化的类的对象,但是又需要反序列化的时候获取这个对象。此时可以使用这里提到的方法进行序列化。

    step1:将不可序列化的类的对象声明为transient,以避免NotSerializableException。示例:

    (student可序列化,teacher不可)

    public class Student implements Serializable {
    
        private String name;
        private int age;
        private transient Teacher teacher;
                      …………
    }

    step2:writeObject方法中调用defaultWriteObject方法写出可序列化的域,然后使用标准的DataOutput调用写出不可序列化的域中的数据。defaultWriteObject是ObjectOutputStream类的一个特殊方法,只能在可序列化类的writeObject方法中调用。注意该方法为private类型。示例:

        private void writeObject(ObjectOutputStream out) throws IOException {
            //对可序列化的域进行序列化
            out.defaultWriteObject();
            //不可序列化的域对该域内基本数据类型分别进行序列化
            out.writeInt(teacher.getCourseNo());
            out.writeUTF(teacher.getName());
        }

    step3:readObject方法中反过来执行上述过程注意该方法也为private类型。示例:

        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
            //对可序列化的域进行反序列化
            in.defaultReadObject();
            //不可序列化的域通过对该域内基本数据类型分别进行反序列化,然后重新创建该域
            int courseNo = in.readInt();
            String name = in.readUTF();
            teacher = new Teacher(courseNo, name);
        }

    完整示例代码:

    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    
    public class Student implements Serializable {
    
        private String name;
        private int age;
        private transient Teacher teacher;
        public Student(){}
    
        public void setName(String n, int a){
            this.name = n;
            this.age = a;
        }
        public String getName(){
            return this.name;
        }
    
        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
            //对可序列化的域进行反序列化
            in.defaultReadObject();
            //不可序列化的域通过对该域内基本数据类型分别进行反序列化,然后重新创建该域
            int courseNo = in.readInt();
            String name = in.readUTF();
            teacher = new Teacher(courseNo, name);
        }
        
        private void writeObject(ObjectOutputStream out) throws IOException {
            //对可序列化的域进行序列化
            out.defaultWriteObject();
            //不可序列化的域对该域内基本数据类型分别进行序列化
            out.writeInt(teacher.getCourseNo());
            out.writeUTF(teacher.getName());
        }
    }
    Student.java
    public class Teacher {
        private int courseNo;
        private String name;
    
        public int getCourseNo() {
            return courseNo;
        }
    
        public String getName() {
            return name;
        }
    
        public Teacher(int courseNo, String name){
            this.courseNo = courseNo;
            this.name = name;
        }
    
    }
    Teacher.java

    注意,

    方法(二)-- External接口

    3、使用Externalizable接口

    无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。除了让序列化机制来保存和恢复对象,类还可以定义它自己的机制。JDK中提供了另一个序列化接口--Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。声明实现Externalizable接口的类必须重写readExternal()和writeExternal()方法。示例:

    import java.io.Externalizable;
    import java.io.IOException;
    import java.io.ObjectInput;
    import java.io.ObjectOutput;
    
    public class Student2 implements Externalizable {
        public String name;
        public int age;
        public Teacher teacher;
        public Student2(){}
    
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(name);
            out.writeInt(age);
        }
    
        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            name = in.readUTF();
            age = in.readInt();
    
        }
    }

    Externalizable继承于Serializable,跟readObject方法不同的是,使用该接口时,序列化的细节需要程序员自行完成,writeExternal和readExternal方法需要对包括超类数据在内的整个对象的存储和恢复负全责。

    在写出对象时,序列化机制在输出流中仅仅是记录该对象所属的类。在读入可外部化的类时,对象输入流会调用类的无参构造函数创建一个对象,然后调用readExternal方法。所以实现Externalizable的类必须提供一个无参构造函数,并且权限为public

    注意:readObject和writeObject是私有的,并且只能被序列化机制调用;readExternal 和 writeExternal是公共的,readExternal还潜在的允许修改现有对象的状态。

    三、总结

    1、java序列化就是为了存储或传输对象的某个瞬时状态。

    2、java中实现对象序列化的方式有两种:

    方法一:

      可序列化类实现Serializable接口,该接口默认情况下没有需要实现的方法。

      如果有不可序列化的字段,如果该字段在不需要反序列化,则使用transient关键字声明该字段即可;如果该字段在反序列化后还有需要,则通过在可序列化类中定义writeObject和readObjcet方法,将不可序列化的字段中的数据分别进行序列化后,在反序列化时读取数据并重新创建对象。

    方法二:

      可序列化类实现Extrernal接口,该接口必须实现readExternal和weiteExternal方法。并在该方法内对每个需要进行序列化的字段挨个进行序列化,反序列化时再挨个反序列化恢复。

  • 相关阅读:
    python-day49--前端 css-层叠样式表
    python-day49--前端 html
    python-day48--mysql之视图、触发器、事务、存储过程、函数
    python-day47--pymysql模块
    python-day47--mysql数据备份与恢复
    python-day46--前端基础之html
    python-day45--mysql索引
    window系统下远程部署Tomcat
    tomcat下部署应用helloworld
    tomcat配置文件context.xml和server.xml分析
  • 原文地址:https://www.cnblogs.com/Jing-Wang/p/10779261.html
Copyright © 2011-2022 走看看