在路上

 找回密码
 立即注册
在路上 站点首页 学习 查看内容

Java程序员必知的Java对象的序列化与反序列化

2017-2-9 13:06| 发布者: zhangjf| 查看: 623| 评论: 0

摘要:   Java对象的序列化与反序列化   序列化与反序列化   序列化(Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等。在网 ...
  Java对象的序列化与反序列化

  序列化与反序列化

  序列化(Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等。在网络传输过程中,可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象。这个相反的过程又称为反序列化。

  Java对象的序列化与反序列化

  在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用该对象。但是,我们创建出来的这些Java对象都是存在于JVM的堆内存中的。只有JVM处于运行状态的时候,这些对象才可能存在。一旦JVM停止运行,这些对象的状态也就随之而丢失了。

  但是在真实的应用场景中,我们需要将这些对象持久化下来,并且能够在需要的时候把对象重新读取出来。Java的对象序列化可以帮助我们实现该功能。

  对象序列化机制(objectserialization)是java语言内建的一种对象持久化方式,通过对象序列化,可以把对象的状态保存为字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式再转换成对象。对象序列化可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。

  在Java中,对象的序列化与反序列化被广泛应用到RMI(远程方法调用)及网络传输中。

  相关接口及类

  Java为了方便开发人员将Java对象进行序列化及反序列化提供了一套方便的API来支持。其中包括以下接口和类:

  java.io.Serializable

  java.io.Externalizable

  ObjectOutput

  ObjectInput

  ObjectOutputStream

  ObjectInputStream

  Serializable接口

  类通过实现java.io.Serializable接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。(该接口并没有方法和字段,为什么只有实现了该接口的类的对象才能被序列化呢?)

  当试图对一个对象进行序列化的时候,如果遇到不支持Serializable接口的对象。在此情况下,将抛出NotSerializableException。

  如果要序列化的类有父类,要想同时将在父类中定义过的变量持久化下来,那么父类也应该集成java.io.Serializable接口。

  下面是一个实现了java.io.Serializable接口的类

  packagecom.hollischaung.serialization.SerializableDemos;

  importjava.io.Serializable;

  /**

   *Createdbyhollison16/2/17.

   *实现Serializable接口

   */

  publicclassUser1implementsSerializable{



    privateStringname;

    privateintage;



    publicStringgetName(){

      returnname;

    }



    publicvoidsetName(Stringname){

      this.name=name;

    }



    publicintgetAge(){

      returnage;

    }



    publicvoidsetAge(intage){

      this.age=age;

    }



    @Override

    publicStringtoString(){

      return"User{"+

          "name='"+name+'''+

          ",age="+age+

          '}';

    }

  }

  通过下面的代码进行序列化及反序列化

  packagecom.hollischaung.serialization.SerializableDemos;



  importorg.apache.commons.io.FileUtils;

  importorg.apache.commons.io.IOUtils;



  importjava.io.*;

  /**

   *Createdbyhollison16/2/17.

   *SerializableDemo1结合SerializableDemo2说明一个类要想被序列化必须实现Serializable接口

   */

  publicclassSerializableDemo1{



    publicstaticvoidmain(String[]args){

      //InitializesTheObject

      User1user=newUser1();

      user.setName("hollis");

      user.setAge(23);

      System.out.println(user);



      //WriteObjtoFile

      ObjectOutputStreamoos=null;

      try{

        oos=newObjectOutputStream(newFileOutputStream("tempFile"));

        oos.writeObject(user);

      }catch(IOExceptione){

        e.printStackTrace();

      }finally{

        IOUtils.closeQuietly(oos);

      }



      //ReadObjfromFile

      Filefile=newFile("tempFile");

      ObjectInputStreamois=null;

      try{

        ois=newObjectInputStream(newFileInputStream(file));

        User1newUser=(User1)ois.readObject();

        System.out.println(newUser);

      }catch(IOExceptione){

        e.printStackTrace();

      }catch(ClassNotFoundExceptione){

        e.printStackTrace();

      }finally{

        IOUtils.closeQuietly(ois);

        try{

          FileUtils.forceDelete(file);

        }catch(IOExceptione){

          e.printStackTrace();

        }

      }



    }

  }



  //OutPut:

  //User{name='hollis',age=23}

  //User{name='hollis',age=23}

  更多关于Serializable的使用,请参考代码实例

  Externalizable接口

  除了Serializable之外,java中还提供了另一个序列化接口Externalizable

  为了了解Externalizable接口和Serializable接口的区别,先来看代码,我们把上面的代码改成使用Externalizable的形式。

  packagecom.hollischaung.serialization.ExternalizableDemos;



  importjava.io.Externalizable;

  importjava.io.IOException;

  importjava.io.ObjectInput;

  importjava.io.ObjectOutput;



  /**

   *Createdbyhollison16/2/17.

   *实现Externalizable接口

   */

  publicclassUser1implementsExternalizable{



    privateStringname;

    privateintage;



    publicStringgetName(){

      returnname;

    }



    publicvoidsetName(Stringname){

      this.name=name;

    }



    publicintgetAge(){

      returnage;

    }



    publicvoidsetAge(intage){

      this.age=age;

    }



    publicvoidwriteExternal(ObjectOutputout)throwsIOException{



    }



    publicvoidreadExternal(ObjectInputin)throwsIOException,ClassNotFoundException{



    }



    @Override

    publicStringtoString(){

      return"User{"+

          "name='"+name+'''+

          ",age="+age+

          '}';

    }

  }

  packagecom.hollischaung.serialization.ExternalizableDemos;



  importjava.io.*;



  /**

   *Createdbyhollison16/2/17.

   */

  publicclassExternalizableDemo1{



    //为了便于理解和节省篇幅,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记

    //IOException直接抛出

    publicstaticvoidmain(String[]args)throwsIOException,ClassNotFoundException{

      //WriteObjtofile

      ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("tempFile"));

      User1user=newUser1();

      user.setName("hollis");

      user.setAge(23);

      oos.writeObject(user);

      //ReadObjfromfile

      Filefile=newFile("tempFile");

      ObjectInputStreamois= newObjectInputStream(newFileInputStream(file));

      User1newInstance=(User1)ois.readObject();

      //output

      System.out.println(newInstance);

    }

  }

  //OutPut:

  //User{name='null',age=0}

  通过上面的实例可以发现,对User1类进行序列化及反序列化之后得到的对象的所有属性的值都变成了默认值。也就是说,之前的那个对象的状态并没有被持久化下来。这就是Externalizable接口和Serializable接口的区别:

  Externalizable继承了Serializable,该接口中定义了两个抽象方法:writeExternal()与readExternal()。当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()与readExternal()方法。由于上面的代码中,并没有在这两个方法中定义序列化实现细节,所以输出的内容为空。还有一点值得注意:在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现Externalizable接口的类必须要提供一个public的无参的构造器。

  按照要求修改之后代码如下:

  packagecom.hollischaung.serialization.ExternalizableDemos;



  importjava.io.Externalizable;

  importjava.io.IOException;

  importjava.io.ObjectInput;

  importjava.io.ObjectOutput;



  /**

   *Createdbyhollison16/2/17.

   *实现Externalizable接口,并实现writeExternal和readExternal方法

   */

  publicclassUser2implementsExternalizable{



    privateStringname;

    privateintage;



    publicStringgetName(){

      returnname;

    }



    publicvoidsetName(Stringname){

      this.name=name;

    }



    publicintgetAge(){

      returnage;

    }



    publicvoidsetAge(intage){

      this.age=age;

    }



    publicvoidwriteExternal(ObjectOutputout)throwsIOException{

      out.writeObject(name);

      out.writeInt(age);

    }



    publicvoidreadExternal(ObjectInputin)throwsIOException,ClassNotFoundException{

      name=(String)in.readObject();

      age=in.readInt();

    }



    @Override

    publicStringtoString(){

      return"User{"+

          "name='"+name+'''+

          ",age="+age+

          '}';

    }

  }



  packagecom.hollischaung.serialization.ExternalizableDemos;



  importjava.io.*;



  /**

   *Createdbyhollison16/2/17.

   */

  publicclassExternalizableDemo2{



    //为了便于理解和节省篇幅,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记

    //IOException直接抛出

    publicstaticvoidmain(String[]args)throwsIOException,ClassNotFoundException{

      //WriteObjtofile

      ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("tempFile"));

      User2user=newUser2();

      user.setName("hollis");

      user.setAge(23);

      oos.writeObject(user);

      //ReadObjfromfile

      Filefile=newFile("tempFile");

      ObjectInputStreamois= newObjectInputStream(newFileInputStream(file));

      User2newInstance=(User2)ois.readObject();

      //output

      System.out.println(newInstance);

    }

  }

  //OutPut:

  //User{name='hollis',age=23}

  这次,就可以把之前的对象状态持久化下来了。

  如果User类中没有无参数的构造函数,在运行时会抛出异常:java.io.InvalidClassException

  更多Externalizable接口使用实例请参考代码实例

  ObjectOutput和ObjectInput接口

  ObjectInput接口扩展自DataInput接口以包含对象的读操作。

  DataInput接口用于从二进制流中读取字节,并根据所有Java基本类型数据进行重构。同时还提供根据UTF-8修改版格式的数据重构String的工具。

  对于此接口中的所有数据读取例程来说,如果在读取所需字节数之前已经到达文件末尾(endoffile),则将抛出EOFException(IOException的一种)。如果因为到达文件末尾以外的其他原因无法读取字节,则将抛出IOException而不是EOFException。尤其是,在输入流已关闭的情况下,将抛出IOException。

  ObjectOutput扩展DataOutput接口以包含对象的写入操作。

  DataOutput接口用于将数据从任意Java基本类型转换为一系列字节,并将这些字节写入二进制流。同时还提供了一个将String转换成UTF-8修改版格式并写入所得到的系列字节的工具。

  对于此接口中写入字节的所有方法,如果由于某种原因无法写入某个字节,则抛出IOException。

  ObjectOutputStream类和ObjectInputStream类

  通过前面的代码片段中我们也能知道,我们一般使用ObjectOutputStream的writeObject方法把一个对象进行持久化。再使用ObjectInputStream的readObject从持久化存储中把对象读取出来。

  更多关于ObjectInputStream和ObjectOutputStream的相关知识欢迎阅读我的另外两篇博文:深入分析Java的序列化与反序列化、单例与序列化的那些事儿

  Transient关键字

  Transient关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值被设为初始值,如int型的是0,对象型的是null。关于Transient关键字的拓展知识欢迎阅读深入分析Java的序列化与反序列化

  序列化ID

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

  序列化ID在Eclipse下提供了两种生成策略,一个是固定的1L,一个是随机生成一个不重复的long类型数据(实际上是使用JDK工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的1L就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化ID有什么作用呢,有些时候,通过改变序列化ID可以用来限制某些用户的使用。

  参考资料

  维基百科

  理解Java对象序列化

  Java序列化的高级认识

  关注“动力节点Java学院”微信公众号,获取更多相关资讯,现在报名培训,可免费参加Java初级课程,亲身体验这里的学习氛围。



最新评论

小黑屋|在路上 ( 蜀ICP备15035742号-1 

;

GMT+8, 2025-7-10 02:39

Copyright 2015-2025 djqfx

返回顶部