java序列化

1.什么是java序列化

Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,

即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,

并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。
    使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。

必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
    除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。

2.实现一个简单的java序列化程序

1.建立一个枚举类型的Gender.java
public enum Gender {
       MALE,FEMALE
}
2.建立一个Person.java
import java.io.Serializable;

/**
 * Created by hao.su on 14-5-11.
 */
public class Person implements Serializable {


    private String name;
    private  transient Integer age;
    private Gender gender;

    public Person() {
        System.out.println("non-arg constructor");
    }

    public Person(String name, Integer age, Gender gender) {
        System.out.println("args constructor");
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

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

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

    public void setGender(Gender gender) {
        this.gender = gender;
    }

    public Integer getAge() {

        return age;
    }

    public Gender getGender() {
        return gender;
    }

    public String getName() {

        return name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                '}';
    }

}

3.建立一个简单的序列化测试类SimpleSerial.java
public class SimpleSerial {
    public static void main(String[] args) {
        File file = new File("person.out");
        //写到文件
        ObjectOutputStream objectOutputStream = null;
        try {
            objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
        } catch (IOException e) {
            e.printStackTrace();
        }
        Person person = new Person("suhao", 23, Gender.MALE);
        try {
            objectOutputStream.writeObject(person);
            objectOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //读取文件
        ObjectInputStream objectInputStream = null;
        try {
            objectInputStream = new ObjectInputStream(new FileInputStream(file));
        } catch (IOException e) {
            e.printStackTrace();
        }
        Object newPerson = null;
        try {
            newPerson = objectInputStream.readObject();
            objectInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println(newPerson);
    }
}

程序运行效果:
args constructor
Person{name='suhao', age=null, gender=MALE}

结论分析: 在java中只要一个类实现了java.io.Serializable接口那么该类就是可以被是实力化的,上面的Gender.java 是
一个枚举类型,我们知道枚举类型是实现了Serializable接口的,那么该类是可以被实例化的
在person类中实现了序列化接口,其中有三个字段
注意到,在反序列化重新读取对象的时候并没有调用构造方法,就像将person对象直接还原一样,我们也可以在其他地方将person还原,但是必须要保证,在classpath中有person.class,否则要抛出ClassNotFoundException

3.Serializable的作用

我们可以看看ObjectOutputStream的源码就知道,作用了,
 if (obj instanceof String) {
		writeString((String) obj, unshared);
	    } else if (cl.isArray()) {
		writeArray(obj, desc, unshared);
	    } else if (obj instanceof Enum) {
		writeEnum((Enum) obj, desc, unshared);
	    } else if (obj instanceof Serializable) {
		writeOrdinaryObject(obj, desc, unshared);
	    } else {
		if (extendedDebugInfo) {
		    throw new NotSerializableException(
			cl.getName() + "\n" + debugInfoStack.toString());
		} else {
		    throw new NotSerializableException(cl.getName());
		}    
	    }
这下知道为什么只有实现了Serializable接口才能被序列化了吧

4.默认的序列化机制

在默认的序列化机制中,不仅会序列化当前本省的对象,而且对象所引用的对象也要被序列化,这些对象引用的其他对象也要被序列化,如此继续。

5.控制序列化的行为

5.1transient 关键字

在person类中的age前面加上transient关键字
private  transient Integer age;

此时再运行程序:
args constructor
Person{name='suhao', age=null, gender=MALE}

发现age的值为空;当一个字段被声明为transient关键字之后,默认的序列化机制,就会忽略该字段
5.2 writeObject 和readObject

  在person类中添加者两个方法

 private void writeObject(ObjectOutputStream out) throws IOException{
        out.defaultWriteObject();
        out.writeInt(age);
    }
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        age = in.readInt();
    }

结果为:

args constructor
Person{name='suhao', age=23, gender=MALE}

age的值写入了

在writeObject 中先调用了默认的defaultObject 方法,此时age字段将会被忽略,在调用writeInt方法将age字段写入,读取时也是这样。


5.3Externalizable接口

JDK中提供了另一个序列化接口--Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效
将person类修改如下
package org.qunar.training.serializationdemo;

import java.io.*;

/**
 * Created by hao.su on 14-5-11.
 */
public class Person implements Externalizable {


    private String name;
    transient private Integer age = null;
    private Gender gender;

    public Person() {
        System.out.println("non-arg constructor");
    }

    public Person(String name, Integer age, Gender gender) {
        System.out.println("args constructor");
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

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

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

    public void setGender(Gender gender) {
        this.gender = gender;
    }

    public Integer getAge() {

        return age;
    }

    public Gender getGender() {
        return gender;
    }

    public String getName() {

        return name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                '}';
    }

    private void writeObject(ObjectOutputStream out) throws IOException{
        out.defaultWriteObject();
        out.writeInt(age);
    }
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        age = in.readInt();
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

    }
}

在执行之:
args constructor
non-arg constructor
Person{name='null', age=null, gender=null}

从结果中可以看到,调用了两次构造器,并且字段都没有序列化
  Externalizable继承于Serializable,当使用该接口时,序列化的细节需要由程序员去完成。如上所示的代码,由于writeExternal()与readExternal()方法未作任何处理,那么该序列化行为将不会保存/读取任何一个字段。这也就是为什么输出结果中所有字段的值均为空。
    另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Person类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public
修改实现Person类中的两个方法:
 @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(age);
        out.writeObject(gender);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = in.readInt();
        gender = (Gender) in.readObject();

    }

5.4readResolve方法

序列化不能保证单列模式的特性,要想保证单列模式的特性,就用readResolve来解决
private Object readResolve() throws ObjectStreamException {
        return InstanceHolder.instatnce;
    }

6.序列化中各种问题

1.序列化ID问题

虚拟时候允许反序列化,不仅取决于类路径是否一致,一个非常中重要的一点事两个类的序列化ID是否一致

2.静态变量序列化问题

序列化不保存静态变量,这其实比较容易理解,序列化保持的是对象的状态,而静态变量是属于类的状态

3.父类序列化

一个子类实现serializable接口,他的父类没有实现,序列化该子类的时候,然后反序输出父类定义的字段,该字段的值于序列化的时候不同
要想让父类进行序列化,也让父类进行序列化

4.敏感字段的加密

在序列化过程中,jvm会调用对象的writeObject和readObject方法,进行用户自定义的序列化和反序列化,如果没有这样的方法,那么默认调用ObjectoutputStream的defaultWriteObject方法以及defaultReadObject方法

5.序列化存储规则

将同一个对象两次写入文件,打印存储第一次存储大小和 写第二次后的大小,然后从文件中反序列化出两个对象,比较这两个对象是否为同一个对象,结果是第二次邪热文件只是增加了5个字节,并且两个对象时相等。
这是为什么呢?
java序列化机制为了节省磁盘空间,当些人文件的为同一个对象时,并不会为对象的内容进行存储,而只是在存储一份引用,上面郑家的5个字节的存储问题,就是新增应用和一些控制信息的空间,反序列化时候恢复引用
将一个对象两次保存到文件中,在第一次保存之后,修改对象的一个字段,在反序列化进行输出时候发现都是修改前的值。这是为什么呢?因为在第二次保存的时候虚拟机发现已经有这样一个对象了,所以第二次保存的只是一个引用,所以读取时都是第一次的对象。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值