java对象的序列化机制详解

Java对象的序列化机制

Java对象的序列化,是将内存中的java对象转化为二进制的字节流,然后保存到磁盘中或者在网络上。这就是序列化对象,反序列化顾名思义就是将对象的二进制字节流恢复成原来的对象。注意只有对象的类名和属性能被序列化(包括基本类型,数组,对其他对象的引用)不包括方法,static属性(静态属性),transient属性(瞬态属性)都不会被序列化。

那什么叫做序列化的对象呢,在Java中不是所有类都是序列化类,如果一个类要实现序列化就必须实现下面两个接口之一:

(1)Serializable接口 (2)Externalizable接口

对于这两个接口的区别后面就会知道的,我们先把类都实现Serializable接口,这个接口java没有提供任何的方法,java设计他只是作为一个类的序列化的标志,表明此类可以进行序列化。

如果要讲java对象转化为二进制的字节流并写出,就一定需要对象的流来进行输出,所以这里用到ObjectOutputStream类,这个输出流是一个处理流,所以需要创建一个字节输出流然后用这个处理流进行包装,所以处理流也叫作包装流。反之ObjectInputSteam是将对象写入的类。写出的对象方法是ObjectOutputStem对象.writeObject(序列化对象的实例)。写入的方法是ObjectInputStream的对象.readObject(序列化的对象);说了这么多,我就做一个例子吧。。

代码:创建一个实现序列化的Person类

public class Person implements Externalizable {
    private String name;
    private  int age;
    public Person1(String name,int age) {
        this.name=name;
        this.age=age;
                //这个地方用于测试序列化和反序列化对象时候实例化类的情况
        System.out.println("带参数的构造器的使用");
    }
    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
  }

代码:创建测试类,进行测试

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

        //如果处理流,当关闭流的时候就不需要再将节点流进行关闭了
        //这是对对象进行写出的操作(序列化对象)
        FileOutputStream fos=null;
        ObjectOutputStream oos=null;
        try {
            fos=new FileOutputStream(new File("E://javatest.txt"));
            oos=new ObjectOutputStream(fos);
            Person per=new Person("孙悟空",66);
            oos.writeObject(per);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            if(oos!=null) {
                oos.close();
            }
        }
        //这是对对象进行写入的操作(反序列化对象)
        FileInputStream fis=null;
        ObjectInputStream ois=null;
        try {
            fis=new FileInputStream("E://javatest.txt");
            ois=new ObjectInputStream(fis);//处理流将文件的字节流进行包装
            //将对象反序列化写入的时候得到的都是Object类型的数据,必须进行强制类型的转化
            Person per=(Person)ois.readObject();
            System.out.println("姓名"+per.getName()+"年龄"+per.getAge());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(ois!=null) {
                ois.close();
            }
        }
    }

readObject方法会抛出ClassNotFoundException异常,也就是说当反序列化时候找不到对应的java类会将引发这个异常,因为反序列化读取的仅仅是java对象的数据,而不是java类,因此采用反序列化恢复java对象时,必须提供该java对象所属类的class文件,否则就会引发该异常。

输出结果可以看出当反序列化的时候构造器没有执行,也就是反序列化类无需通过构造器来进行初始化java对象

注:如果我们向文件中使用序列化机制写入多个java的对象,使用反序列化机制恢复对象时必须按实际的写入顺序读取。

Y(^o^)Y属性是引用类型的对象的序列化

我们上面所说的属性都是String类型和基本类型,如果我们需要一个引用类型呢,那么这个引用类型也必须是可序列化的类,否则拥有该类型的属性类不可序列化。下面我将要定义一个引用类型的属性,重新创建一个Teacher类,引用属性必须实现序列化,否则Treacher不论实现不实现(1)Serializable接口 (2)Externalizable接口这两个接口,他都不是序列化的类,因为当对象序列化的时候,会顺带着把引用类型的属性进行序列化,所以要想Teacher是序列化的类,则必须将Person的类进行序列化。

代码Teacher类

public class Teacher implements Serializable {

    private String name;
    private Person student;//引用类型的属性(这个Person类就是上面实现序列化的类)
    public Teacher(String name,Person student) {
        this.name=name;
        this.student=student;
        System.out.println("带参数的构造器的使用");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Person getStudent() {
        return student;
    }
    public void setStudent(Person student) {
        this.student = student;
    }
}

代码 测试代码

public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub

        FileOutputStream fos=null;
        ObjectOutputStream oos=null;
        Person p=new Person("孙悟空",66);
        Teacher t1=new Teacher("玄奘法师",p);
        Teacher t2=new Teacher("菩提祖师",p);
        try {
            fos=new FileOutputStream(new File("E://javatext.txt"));
            oos=new ObjectOutputStream(fos);
            oos.writeObject(t1);
            oos.writeObject(t2);
            oos.writeObject(p);
            oos.writeObject(t1);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if(oos!=null) {
                oos.close();
            }
        }
    }
}

分析这段代码可以看出我创建出来了三个类。Person p=new Person("孙悟空",66);Teacher t1=new Teacher("玄奘法师",p);Teacher t2=new Teacher("菩提祖师",p);如果我将这三个类进行对象的序列化的话,t1写出并且Person类也会进行序列化,同理t2也是,然后我们又显示序列化了Person类,所以Person类在此次写出中,被序列化了三回,那么对于t1,t2来说实际上他们的Person类是同一个,但是如果Person序列化三回的话,t1,t2就没有引用同一个Person类,这显然是不符合实际情况的。Java对此采用了一种特殊的序列化算法,算法的内容是:

(1)所有保存到磁盘中的对象都有一个序列化编号。

(2)当程序师徒序列化一个对象的时候,程序将先检查对象是否已经序列化过,只有当该对象从未(在本次虚拟机中)被序列化过,系统才会将该对象转化成字节序列并输出。

(3)如果某个对象是已经序列化过的,程序将直接只是输出一个序列化编号,而不是重新序列化该对象。

Y(^o^)Y序列化的对象是可变的类

根据java的序列化机制,当我先写进去序列化的时候,如果我改变了可变类的属性值,那么当我想再次进行序列化的时候就不能把更改后的值序列化了,因为java的序列化机制当在此序列化同一的对象的时候,输出的是一个序列化编号。程序会比较两个对象是同一个对象,就不会把对象重新的序列化。就是更改后的对象并没有被写入。这再次验证了java的序列化机制。

代码 测试类

public class VolatileClassTest {

    /**
     * 序列化可变类
     * @param args
     * @throws IOException 
     * @throws ClassNotFoundException 
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // TODO Auto-generated method stub

        FileOutputStream fos=null;
        ObjectOutputStream oos=null;
        FileInputStream fis=null;
        ObjectInputStream ois=null;
        try {
            
            //进行序列化的操作
            fos=new FileOutputStream(new File("E://javatest.txt"));
            oos=new ObjectOutputStream(fos);
            Person p=new Person("孙悟空",600);
            oos.writeObject(p);//将对象进行序列化
            p.setName("红孩儿");//可变类将姓名属性设置为红孩儿,可变类
            oos.writeObject(p);//将更改后的类进行序列化
            
            //进行反序列化操作
            fis=new FileInputStream(new File("E://javatest.txt"));
            ois=new ObjectInputStream(fis);
            Person per=(Person)ois.readObject();
            //输出的姓名还是孙悟空,再次验证了java的序列化机制
            System.out.println("姓名是"+per.getName());
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if(oos!=null) {
                oos.close();
            }
            if(ois!=null) {
                ois.close();
            }
        }
    }
}