Java 序列化

来源:互联网 发布:外国语大学网络教育 编辑:程序博客网 时间:2024/06/11 17:36

Java序列化

Java允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在;也即,这些对象的生命周期不会比JVM的生命周期更长。但在实际应用中,就可能要求在JVM停止运行之后能够持久化指定的对象,并在将来重新读取被保存的对象。

使用Java对象序列化,在保存对象时,会把其状态(只是对象状态,不包括类变量)保存为一组字节,以便再将这些字节组装成对象。

除了在持久化对象时会用到对象序列化之外,当使用RMI,或在网络中传递对象时,都会用到对象序列化。

1、       Serializable接口

在Java中,只要一个类实现了Java.io.Serializable接口,那么它就可以被序列化,如下

/*枚举默认继承java.lang.Enum,而该类实现了Serializable接口

 * */

public enum Gender

{

    FEMALE,

    MALE,

    OTHER

}

 

public class Person   implements Serializable

{

  

   private static final long   serialVersionUID   = 1L;

  

   private int id;

   private Stringname;

   private int age;

   private Gendergender;

   private Stringinfo;

  

   public Person()

   {

     

   }  

/*省略settergetter方法

 * */

}

为什么一个类实现了Serializable接口,就可以被序列化呢?其实底层采用的是ObjectOutputStream类来进行序列化的,其中ObjectOutputStream类中最重要的一个方法为writeObject0,部分代码人如下:

private void writeObject0(Object obj, boolean unshared)

        throws IOException

{

            // …      

            // remaining cases

if (objinstanceof 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

{

     // …   

            }

    }

2、       持久化对象

对于实现Serializable接口的类的实例,可以将对象持久化到磁盘中,在持久化时需要用到ObjectOutputStream流,如下

/*持久化*/

       File file=new File("/Users/ssl/person.out");

       OutputStream outputStream=new FileOutputStream(file);

       ObjectOutputStream objectOutputStream=newObjectOutputStream(outputStream);

       

       Person person=new Person();

       person.setId(1);

       person.setName("ssl");

       person.setAge(18);

       person.setGender(Gender.MALE);

       person.setInfo("ssl");

       

       objectOutputStream.writeObject(person);

       objectOutputStream.close();

       

       /*反序列化*/

       ObjectInputStream objectInputStream=newObjectInputStream(new FileInputStream(file));

       Person p=(Person)objectInputStream.readObject();

       objectInputStream.close();

       

    System.out.println(p);

 

3、       默认序列化机制

如果仅仅只是让某个实现类实现Serializable接口,而没有其他任何处理的话,则就是使用默认序列化机制。使用默认序列化机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其他对象也进行序列化(若属性对象没有实现Serializable接口,将会报错),同样地,若引用对象也引用其他的对象,这些对象都会被序列化。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含的元素也是容器类,那么序列化的过程就会复杂,开销也大。

此外,如果父类实现了Serializable接口,其子类都可以被序列化;若子类实现了Serializable接口,而父类没有,则父类中的属性不能被序列化,子类中的属性仍能够被序列化,结果导致父类的信息丢失。在纯Java环境下,Java序列换能够很好的工作,但是在跨平台的系统中,最好采用通用存储数据结构,如JSON或XML等。


3.1、transient

若是某个属性不想被序列化,则可以加上transient关键字。默认序列化机制就会忽略该字段。

3.2、writeObject、readObject

在序列化和反序列化过程中会调用到writeObjectreadObject方法,这两个方法为私有方法,在序列化和反序列化过程中会通过反射机制来调用。所以在序列化类中添加这些方法,可以改变默认的序列化机制

public class Person   implements Serializable

{

  

   private static final long   serialVersionUID   = 1L;

  

   private int id;

   private Stringname;

   transient private int age;

   private Gendergender;

   private Stringinfo;

  

   public Person()

   {

     

   }

 

/*添加writeObjectreadObject方法会影响默认的序列化机制*/

private void writeObject(ObjectOutputStream out)throws IOException

{

/*defaultWriteObject会执行默认的序列化机制*/

   out.defaultWriteObject();

   out.writeInt(age);

}

 

private void readObject(ObjectInputStream in)throwsClassNotFoundException, IOException

{

  in.defaultReadObject();

  age=in.readInt();

}

 

}

3.3、Externalizable接口

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

Externalizable继承Serializable,当使用该接口时,序列化的细节需要由程序员显示完成,重写

public void writeExternal(ObjectOutput out)throws IOException

public void readExternal(ObjectInput in)throws IOException,

      ClassNotFoundException

等方法来完成序列化操作。

当使用Externalizable进行序列化时,读取对象时,会调用序列化类无参的构造函数来创建一个新的对象,然后再将被保存对象的字段的值填充到新对象中,所以序列化类最好要提供无参的构造函数(默认的构造函数)。

 

3.4、readResolve

当我们使用单例模式时,应该是期望某个类的实例是唯一的,但如果该类是可序列化的,那么情况可能会略有不同。

当从文件中反序列化得到的对象与原单例对象并不是一个对象,为了能在序列化过程中仍保持单例的特性,可以在单例类中添加一个readResolve()方法,在该方法中直接返回Person的单例对象。

private Object readResolve();

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

4、       高级认识

将Java对象序列化为二进制文件,是Java序列化的本质。在不部分情况下,开发人员只需要了解被序列化的类实现Serializable接口,使用ObjectInputStream和ObjectOutputStream进行对象的读写。然而,在某些情况下,知道这些是远远不够的,下面列举一些Java序列化中的高级知识。

4.1、序列化ID

序列化可以使Java对象在网络中传输,在A端序列化的对象经过网络传向B端,在B端反序列化为对象,此时要求A段和B段都有被序列化的类或.class文件。若此时,被序列化的类中没有显示指定序列化ID会出现什么情况呢?

虚拟机是否允许序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点的是两个类的序列化ID是否一致

private static final long serialVersionUID   =1L;

若两个类的序列化ID不一致,他们之间是无法相互序列化和反序列化的。简单来说,Java的序列化机制是通过在运行时判断类的序列化ID来验证版本一致性的。在进行反序列化时,JVM会把传来字节流中的序列化ID与本地对应的类中序列化ID进行比较。如果相同就认为是一致的,可以进行反序列化;否则就会出现序列化版本不一致的异常。

4.2、静态变量

在序列化时,并不持久化静态变量。那么,在本地序列化和反序列化时,如何获取静态变量?在网络传输时,又如何获取静态变量。

 

4.3、敏感字段加密

在序列化过程中,虚拟机会试图调用类对象中的writeObject和readObject方法,进行用户自定义的序列化和反序列化。如果没有这些方法,则默认会调用ObjectOutputStream的defaultWriteObject方法以及ObjectInputStream的defaultReadObject方法。基于该过程,我们自定义writeObject和readObject方法,来改变序列化的数值,如用于敏感字段的加密工作。

4.4、序列化存储规则

Java序列化机制为了节省磁盘空间,具有特定的存储规则。当写入文件的为同一个对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用。反序列化时,恢复引用关系。


 

0 0