您的位置:首页 > 编程语言 > Java开发

Java 对象序列化详解以及实例实现和源码下载

2015-03-16 16:45 1056 查看
Java中的序列化机制有两种实现方式:

一种是实现Serializable接口

另一种是实现Externalizable接口

区别:

实现Serializable接口

1 系统自动储存必要的信息

2 Java内建支持,易于实现,只需实现该接口即可,无须任何代码支持

3 性能略差

实现Externalizable接口

1 程序员决定存储哪些信息

2 仅仅提供两个空方法,实现该接口必须为两个空方法提供实现

3 性能略好



由于实现Externalizable接口导致了编程复杂度的增加,所以大部分时候采用实现Serializable接口方式实现序列化。

下面给出一个实例

public class PersonBean implements Serializable {
    private String name;
    private int age;
    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;
    }
    @Override
    public String toString() {
        return "name="+this.name + ",age=" + this.age;
    }
}


这是一个PersonBean类,就这么简单已经实现了PersonBean类的序列化。

下面就可以使用ObjectInputStream、ObjectOutputStream类进行对象的写入和读取操作了。

代码如下:

public static void main(String[] args) {
        PersonBean personBean1 = new PersonBean();
        personBean1.setName("long");
        personBean1.setAge(20);
        PersonBean personBean2 = new PersonBean();
        personBean2.setName("fei");
        personBean2.setAge(25);
        ObjectOutputStream objectOutputStream = null;
        ObjectInputStream objectInputStream = null;
        String path = "D:\\Program Files (x86)\\ADT\\workspace\\JavaIO\\demoTest.txt";
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(path);
            FileInputStream fileInputStream = new FileInputStream(path);
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(personBean1);
            objectOutputStream.writeObject(personBean2);

            objectInputStream = new ObjectInputStream(fileInputStream);
            PersonBean personBean3 = (PersonBean)objectInputStream.readObject();
            PersonBean personBean4 = (PersonBean) objectInputStream.readObject();
            System.out.println(personBean3.toString());
            System.out.println(personBean4.toString());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally{
            if (objectOutputStream != null) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


结果图:





Java中允许自定义序列化,提供了一个修饰符transient,该修饰符只能修饰Field字段,不能修饰方法和类,该修饰符达到的效果是把被修饰的Field完全隔离在序列化机制之外。这样导致在反序列化回复java对象时无法取得该Field值。

现在把PeronBean类的age字段使用该修饰符修饰,其他代码保持不变如下:

public class PersonBean implements Serializable {
    private String name;
    private transient int age;
    ..............


然后再运行程序Demo,结果如下:




看到了结果大家应该明白了该修饰符的意义了吧。



自定义序列化机制还没有这么简单,还可以更强大。

在序列化和反序列化过程中需要特殊处理的类提供如下特殊签名的方法,这些特殊的方法用以实现自定义序列化

private void writeObject(java.io.ObjectOutputStream) throws IOException
private void readObject(java.io.ObjectInputStream) throws IOException,ClassNotFoundException
private void readObjectNoData() throws ObjectStreamException


writeObject方法负责写入特定累的实例状态,readObject方法可以回复它

当序列流不完整时,readObjectNoData方法可以用来正确地初始化反序列化的对象:例如,接收方使用的反序列化类的版本不用于发送方,或者接收方版本扩展的类不是放松方版本扩展的类,或者序列化流被篡改时,系统都会调用readObjectNoData方法类初始化反序列化的对象。

下面重写PersonBean类,自定义序列化,代码的如下:

public class PersonBean implements Serializable {
    private String name;
    private transient int age;
    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;
    }

    private void writeObject(ObjectOutputStream out) throws IOException{
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }

    private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{
        this.name = ((StringBuffer)in.readObject()).toString();
        this.age = in.readInt();
    }
    @Override
    public String toString() {
        return "name="+this.name + ",age=" + this.age;
    }
}


writeObject方法,把名字反转后写入,readObject方法直接读取反转后的名字即可。Demo类代码不变,结果如图:



从结果可以看出自定义序列化已经实现,并且writeObject、readObject也已经被调用了。

细心的读者可能发现,PersonBean类中int age使用了transient修饰符修饰了,但是读取之后的结果age不等于0??前面讲过,使用transient修饰符修饰的field是完全隔离的。但是这里需要注意的是,这里的序列化是自定义的,自定义的过程中并没有对该字段进行处理,当然transient修饰符在这里并不起作用了。。。



还有一种更加彻底的序列化自定义机制

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException


该方法前面的ANY-ACCESS-MODIFIER说明该方法可以用于private protected package-private等访问权限,其子类可能获得该方法。

下面重写PersonBean类,如下:

public class PersonBean implements Serializable {
    private String name;
    private transient int age;
    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;
    }

    private void writeObject(ObjectOutputStream out) throws IOException{
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }

    private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{
        this.name = ((StringBuffer)in.readObject()).toString();
        this.age = in.readInt();
    }
    @Override
    public String toString() {
        return "name="+this.name + ",age=" + this.age;
    }

    private Object writeReplace() throws ObjectStreamException{
        ArrayList<Object> arrayList = new ArrayList<>();
        arrayList.add(this.name);
        arrayList.add(this.age);
        return arrayList;
    }
}


Java的序列化机制保证在序列化某个对象之前,先调用该对象的writeReplace方法,如果该方法返回一个Java对象,则系统转为序列化另一个Java对象。上面代码中序列化personbean对象,但是writeReplace方法返回ArrayList对象,所以序列化保存的是ArrayList对象。那么读取保存的序列化对象时获取的当然也是ArrayList对象,所以Demo类代码改为如下:

public class ObjectDemo {
    public static void main(String[] args) {
        PersonBean personBean1 = new PersonBean();
        personBean1.setName("long");
        personBean1.setAge(20);
        PersonBean personBean2 = new PersonBean();
        personBean2.setName("fei");
        personBean2.setAge(25);
        ObjectOutputStream objectOutputStream = null;
        ObjectInputStream objectInputStream = null;
        String path = "D:\\Program Files (x86)\\ADT\\workspace\\JavaIO\\demoTest.txt";
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(path);
            FileInputStream fileInputStream = new FileInputStream(path);
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(personBean1);
            objectOutputStream.writeObject(personBean2);

            objectInputStream = new ObjectInputStream(fileInputStream);
//          PersonBean personBean3 = (PersonBean)objectInputStream.readObject();
//          PersonBean personBean4 = (PersonBean) objectInputStream.readObject();
            ArrayList<Object> personBean3 = (ArrayList<Object>)objectInputStream.readObject();
            ArrayList<Object> personBean4 = (ArrayList<Object>) objectInputStream.readObject();
            System.out.println(personBean3.toString());
            System.out.println(personBean4.toString());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally{
            if (objectOutputStream != null) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


结果:





与writeReplace方法相对的是

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException


该方法可以实现保护性复制整个对象。该方法在序列化单例类、枚举类时尤其有用。大家想想为什么??

原因在于,单例类和枚举类希望反序列化之后返回的还是原来的对象,但是在不实用该方法的情况下,反序列化之后放回的是原来的对象的克隆!!不是原来的对象!该方法就可以解决这个问题,返回原来序列化的同一个对象!!是不是很强大!



另一种实现自定义序列化的机制是使用Externalizable接口,实现该接口需要实现两个方法

void readExternal(ObjectInput in)

void writeExternal(ObjectOutput out)

这两个方法和签名使用readObject writeObject方法非常相似,只是Externalizable接口强制使用自定义序列化!除了重写这两个方法之外,其他操作都一样,也就不举例子了。。



关于序列化机制 需要注意的几点:

1、对象的类名、Field都会被序列化,方法、static field、transient field都不会被序列化

2、实现Serializable接口使用transient修饰符修饰field可以不被序列化,虽然static 修饰符可以达到同样的效果,但是static transient不能混用。

3、反序列化时必须有序列化对象的class文件!!!

4、当通过文件、网络来读取序列化后的对象时,必须按实际写入的顺序读取。



Java序列化机制允许为序列化类提供一个private static final 的serialVersionUID值,该值用于标识该类的序列化版本。如果一个类升级了,只要它的serialVersionUID Field值保持不变,序列化机制也会把他们当成一个序列化版本。

为了在反序列化时确保序列化版本的兼容性,最好在每个要序列化的类中加入private static final long serialVersionUID这个field,它的具体值可以自己定义。

不显式指定serialVersionUIR Field值的一个坏处,serialVersionUID值有JVM根据类的相关信息计算,而修改后的类的计算结果与修改前的类的计算结果往往不同,从而造成对象的反序列化因为类版本的不兼容而失败。

另外一个坏处是,不利于程序在不用的JVM之间移植。因为不同的编译器计算该值的计算策略可能不同,从而造成虽然累完全没有改变,但是因为JVM不同,也会出现序列化版本不兼容而无法正确反序列化的现象。

有这么集中情况需要讨论:

1、如果修改类是仅仅修改了方法,则反序列化不受影响。无须指定serialVersionUID值

2、如果修改类时仅仅修改了静态Field或瞬态Field,则反序列化不受影响。无须指定serialVersionUID值。

3、如果修改类时修改了非静态的Field 非瞬态Field,则可能导致序列化版本不兼容。如果对象流中的对象和新类中包含同名的Field,而类型不同,则反序列化失败,类定义应该更新serialVersionUID值。如果对象流中包含比新类中更多的Field,则多出的Field值被忽略,序列化版本可以兼容,类定义可以不更新serialVersionUID值。如果新类比对象流中的对象包含更多的Field,则序列化版本也可以兼容,不用指定serialVersionUID值,但反序列化得到的新类的对象中多出的Field值为null或者0。

源码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: