java序列化学习

定义:
Java序列化是指把Java对象转换为字节序列的过程,保存在硬盘中;而Java反序列化是指把字节序列恢复为Java对象的过程。

优点:
好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。

JDK类库中序列化API

java.io.ObjectOutputStream:表示对象输出流

它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。(将对象序列化保存到文件中)

java.io.ObjectInputStream:表示对象输入流

它的readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。

简单例子

1、先创建一个枚举类


public enum Gender {
    MALE,FEMALE
}

2、创建一个Person类。
注意:
如果被写对象的类型是String,或数组,或Enum,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。
序列化是指对象的序列化,只会关注对象的属性,不会关注类中的静态变量

public class Person implements Serializable{
    private String name;

    private Integer age;

    private Gender gender;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public Gender getGender() {
        return gender;
    }

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

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

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

    @Override 
    public String toString() {  
        return "[" + name + ", " + age + ", " + gender + "]";  
    } 

}

3、创建一个测试类:

public class SimpleSerialTest {

    public static void main(String[] args) {
        //新建一个文件
        File file = new File("person.out");

        try {
            ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
            Person person = new Person("john", 21, Gender.MALE);
            oout.writeObject(person);
            oout.close();
            //但必须确保该读取程序的CLASSPATH中包含有Person.class
            //(哪怕在读取Person对象时并没有显示地使用Person类,如上例所示),否则会抛出ClassNotFoundException。
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
            Object per = oin.readObject();
            oin.close();
            System.out.println(per.toString());


        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

上述程序的输出结果:
arg constructor
[john, 21, MALE]

*此时必须注意的是,当重新读取被保存的Person对象时,并没有调用Person的任何构造器,看起来就像是直接使用字节将Person对象还原出来的。
当Person对象被保存到person.out文件中之后,我们可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的CLASSPATH中包含有Person.class(哪怕在读取Person对象时并没有显示地使用Person类,如上例所示),否则会抛出ClassNotFoundException。*

默认的序列化机制
一个类被序列化,不仅他本身被序列化,还会对该对象引用的其他对象也将被序列化,所以当一个类对象引用了某个容器类对象,而容器对象所包含的元素也是容器对象,当被序列化的时候,会出现将容器对象也序列化,此时会带来很大的内存开销。

在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。如:transient关键字、writeObject()方法与readObject()方法指定那些属性被序列化和输出、Externalizable接口、readResolve()方法。

1、transient关键字
当某个字段被声明为transient后,默认序列化机制就会忽略该字段。

/**
 * 测试transient功能
 * @author DELL
 *
 */
public class Animal implements Serializable{
    private String name;

    private int age;

    //使用transient,目的是为了避免敏感信息在序列化的时候
    transient private String getter;//持有者
    //测试static类熟悉是否被序列化
    public static int weight;

    public void setGetter(String getter) {
        this.getter = getter;
    }
    ......省略set,get,构造方法
    @Override
    public String toString() {
        return "Animal [name=" + name + ", age=" + age + ", getter=" + getter
                + "]";
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        File fileAnimal = new File("animal.out");
        writeFile(fileAnimal, new Animal("niuniu", 3, "张三"));
        readFile(fileAnimal);
    }

    //序列化公共处理方法
    public static void writeFile(File file,Object obj){
        try {
            ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
            //Person person = new Person("zhangfei", 23, Gender.FEMALE);
            oout.writeObject(obj);
            oout.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    //反序列化公共处理方法
    public static void readFile(File file){
        try {
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
            Object obj =  oin.readObject();
            oin.close();
            System.out.println(obj.toString());
            //System.out.println(SinglePerson.getInstance() == ((SinglePerson)obj));
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

测试结果:
Animal [name=niuniu, age=3, getter=null]
结果可见:Animal类对象中被transient修饰的持有者未被序列化。

writeObject()方法与readObject()方法

public class Person implements Serializable{
    .......
    transient private Integer age;

    ......

    //如果使用了transient关键字,则该属性在序列化时会被忽略,使用私有的writeObject,readObject方法,会让被忽略的属性,再次被序列化。
    private void writeObject(ObjectOutputStream outputStream){
        try {
            outputStream.defaultWriteObject();
            System.out.println("this is a flag");
            outputStream.writeInt(age);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private void readObject(ObjectInputStream inputStream){
        try {
            inputStream.defaultReadObject();
            age = inputStream.readInt();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

测试代码:

public class Test {
    public static void main(String[] args) {
        File file = new File("test.out");
        writeFile(file,new Person("zhangfei", 23, Gender.FEMALE));
        readFile(file);
    }
    ......
}

测试结果:
arg constructor
this is a flag
[zhangfei, 23, FEMALE]

writeObject()方法中会先调用ObjectOutputStream中的defaultWriteObject()方法,该方法会执行默认的序列化机制,如5.1节所述,此时会忽略掉age字段。然后再调用writeInt()方法显示地将age字段写入到ObjectOutputStream中。readObject()的作用则是针对对象的读取,将值设置到属性中,其原理与writeObject()方法相同

Externalizable接口
无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。JDK中提供了另一个序列化接口–Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。

/**
 * 使用Externalizable接口,调用的是无参构造方法构造对象。
 * @author DELL
 *
 */
public class Project implements Externalizable{

    private String projectName;

    private String PorjectLeader;

    private int teamPerson;//团队人数
    ......
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {

    }

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

    }
    public Project() {
        System.out.println("this method had constructor");
    }
    @Override
    public String toString() {
        ......
    }
}

测试代码:
public class Test {
public static void main(String[] args) {
File fileProject = new File(“project.out”);
writeFile(fileProject, new Project(“XXXXXX”, “xiaoming”, 6));
readFile(fileProject);
}
}
输出结果:
this method had constructor
Project [projectName=null, PorjectLeader=null, teamPerson=0]

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

因此对上面的project类做进一步修改:

public class Project implements Externalizable{

    ......将writeExternal,readExternal进行补全。。
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(projectName);
        out.writeInt(teamPerson);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        projectName = (String) in.readObject();
        teamPerson = in.readInt();
    }
    ......

再次执行Test类,执行结果如下:
this method had constructor
Project [projectName=xxxxx, PorjectLeader=null, teamPerson=6]

readResolve()方法

public class SinglePerson implements Serializable{

    private String name;

    private String address;

    private int age;

    private static class InstanceHolder{
        private static final SinglePerson person = new SinglePerson("jack", "tianfulu", 12);
    }

    public static SinglePerson getInstance(){
        return InstanceHolder.person;
    }
    ......
    @Override
    public String toString() {
        return "SinglePerson [name=" + name + ", address=" + address + ", age="
                + age + "]";
    }
}

测试代码:

public class Test {
    public static void main(String[] args) {
        /*File file = new File("test.out");
        writeFile(file,new Person("zhangfei", 23, Gender.FEMALE));
        readFile(file);
        File fileAnimal = new File("animal.out");
        writeFile(fileAnimal, new Animal("niuniu", 3, "张三"));
        readFile(fileAnimal);

        File fileProject = new File("project.out");
        writeFile(fileProject, new Project("xxxxx", "xiaoming", 6));
        readFile(fileProject);*/

        File fileSinglePerson = new File("singlePerson.out");
        writeFile(fileSinglePerson, SinglePerson.getInstance());
        readFile(fileSinglePerson);

    }
    ......

        //反序列化公共处理方法
    public static void readFile(File file){
        try {
            .......
            //新增
            System.out.println(SinglePerson.getInstance() == ((SinglePerson)obj));
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    }

测试结果:
SinglePerson [name=jack, address=tianfulu, age=12]
false

新增readResolve方法

public class SinglePerson implements Serializable{

    private String name;

    private String address;

    private int age;

    ..........省略

    private Object readResolve() throws  ObjectStreamException{
        return SinglePerson.getInstance();
    }
}

再次执行测试代码:
SinglePerson [name=jack, address=tianfulu, age=12]
true

无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。

参考文章:http://developer.51cto.com/art/201202/317181.htm
http://blog.youkuaiyun.com/wangloveall/article/details/7992448/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值