序列化和反序列化序列化(Serialization)是将对象的状态信息转换成可以存储或传输的形式的过程。通常,对象存储在存储介质中,例如文件或内存缓冲区。网络传输时,可以是byte格式,也可以是XML格式。并且字节或XML编码格式可以还原完全相等的对象。这个反向过程也称为反序列化。Java对象的序列化和反序列化在Java中,我们可以通过多种方式创建对象,只要对象不被回收,我们就可以重用该对象。但是,我们创建的Java对象都存在于JVM的堆内存中。这些对象只有在JVM运行时才能存在。一旦JVM停止运行,这些对象的状态也会丢失。但是在实际的应用场景中,我们需要持久化这些对象,并且能够在需要的时候读回这些对象。Java的对象序列化可以帮助我们实现这个功能。对象序列化机制(objectserialization)是Java语言中内置的对象持久化方法。通过对象序列化,可以将对象的状态保存为字节数组,需要的时候可以通过字节数组进行传递。然后将反序列化方法转换为对象。对象序列化使得在JVM中活动对象和字节数组(流)之间的转换变得容易。在Java中,对象序列化和反序列化在RMI(远程方法调用)和网络传输中被广泛使用。相关接口和类Java提供了一套方便的API来支持开发者对Java对象进行序列化和反序列化。其中包括以下接口和类:java.io.Serializablejava.io.ExternalizableObjectOutputObjectInputObjectOutputStreamObjectInputStreamSerializable接口类实现java.io.Serializable接口以启用其序列化功能。不实现此接口的类将无法序列化或反序列化其任何状态。可序列化类的所有子类型本身都是可序列化的。Serializable接口没有方法或字段,仅用于标识可序列化语义。当尝试序列化对象时,如果遇到不支持Serializable接口的对象。在这种情况下,将抛出NotSerializableException。虽然Serializable接口没有定义任何属性和方法,但是如果一个类想要具有序列化能力,就必须实现它。其实主要是在序列化的实际执行过程中,会通过instanceof来判断一个类是否实现了类Serializable,如果没有实现,会直接抛出异常。关于这部分内容,我会单独开一篇文章来说明。如果要序列化的类有父类,并且想同时持久化父类中定义的变量,那么父类也应该集成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;}@OverridepublicStringtoString(){return"User{"+"name='"+name+'\''+",age="+age+'}';}}通过以下代码进行序列化和反序列化packagecom.hollischaung.serialization.SerializableDemos;importjava.io.File;importjava。io.FileInputStream;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;/***Createdbyhollison16/2/17.*SerializableDemo1结合SerializableDemo2说明一个类要是serialized必须实现Serializable接口*/publicclassSerializableDemo1{publicstaticvoidmain(String[]args){//InitializesTheObjectUser1user=newUser1();user.setName("t;hollis");user.setAge(23);System.out.println(user);//WriteObjtoFiletry(FileOutputStreamfos=newFileOutputStream("tempFile");ObjectOutputStreamoos=newObjectOutputStream(fos)){oos.writeObject(user);}catch(IOExceptione){e.printStackTrace();}//ReadObjfromFileFilefile=newFile("tempFile");try(ObjectInputStreamois=newObjectInputStream(newFileInputStream(file))){User1newUser=(User1)ois.readObject();System.out.println(newUser);}catch(IOException|ClassNotFoundExceptione){e.printStackTrace();}}}//OutPut://User{name='hollis',age=23}//User{name='hollis'',age=23}如果你观察的足够仔细,你可能会发现我在上面的测试代码中使用了IO流,但是我并没有明确的关闭他,这其实是Java7的一个新特性try-with-resources.这其实就是Java中的一种语法糖,其背后的原理是编译器已经帮我们完成了关闭IO流的工作,关于如何使用语法糖来提高代码质量,我会在后面单独出一篇文章。Code,我们把代码中定义的User对象通过序列化的方式保存到文件中,然后从文件中序列化成Java对象。结果是我们对象的属性被持久化了。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{}@OverridepublicStringtoString(){return"User{"+"name='"+name+'\''+",age="+age+'}';}}packagecom.hollischaung.serialization。ExternalizableDemos;importjava.io.*;/***Createdbyhollison16/2/17.*对一个实际发现了Externalizable接口的类进入序列化及反序列化*/publicclassExternalizableDemo1{publicstaticvoidmain(String[]args){//WriteObjtofileUser1newUser1();user.setName("霍利斯"t;);user.setAge(23);try(ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("tempFile"))){oos.writeObject(user);}catch(IOExceptione){e.printStackTrace();}//ReadObjfromfileFilefile=newFile("tempFile");try(ObjectInputStreamois=newObjectInputStream(newFileInputStream(file))){User1newInstance=(User1)ois.readObject();//outputSystem.out.println(newInstance);}catch(IOException|ClassNotFoundExceptione){e.printStackTrace();}}}//OutPut://User{name='null',age=0}通过上面例子的输出可以发现User1类经过序列化和反序列化后的值对象的所有属性都变成了默认值,即之前对象的状态没有被持久化。这就是Externalizable接口和Serializable接口的区别:Externalizable继承Serializable,在这个接口中定义了两个抽象方法:writeExternal()和readExternal()。在使用Externalizable接口进行序列化和反序列化时,开发者需要重写writeExternal()和readExternal()方法。由于上述代码在这两个方法中没有定义序列化实现细节,所以输出内容为空。还有一点值得注意:在使用Externalizable进行序列化时,在读取一个对象时,会调用序列化类的无参构造函数来创建一个新对象,然后将保存的对象的字段值分别填充到新对象中。因此,实现Externalizable接口的类必须提供一个公共的无参数构造函数。如果实现Externalizable接口的类中没有无参构造函数,运行时会抛出异常:java.io.InvalidClassException。如果一个Java类没有定义任何构造函数,编译器会自动为我们添加一个无参构造函数。但是,如果我们在类中定义了带参数的构造函数,编译器就不会再帮我们创建没有参数的构造方法,这个需要注意。根据需要修改后,代码如下: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();年龄=在。readInt();}@OverridepublicStringtoString(){return"User{"+"name='"+name+'\''+",age="+age+'}';}}再次执行测试得到如下结果//OutPut://User{name='hollis',age=23}这次可以持久化之前的对象状态。ObjectOutput和ObjectInput接口上的writeExternal方法和readExternal方法分别接收ObjectOutput和ObjectInput类型的参数。这两个类的作用如下。ObjectInput扩展了DataInput接口以包括对对象的读取操作。DataInput接口用于从二进制流中读取字节并从所有Java原始数据类型中重建它们。它还提供了从修改后的UTF-8格式的数据中重建字符串的工具。对于此接口中的所有数据读取例程,如果在读取所需字节数之前到达文件末尾,将抛出EOFException(一种IOException)。如果由于文件结束以外的原??因无法读取字节,将抛出IOException而不是EOFException。特别是,如果输入流关闭,将抛出IOException。ObjectOutput扩展了DataOutput接口以包括对象的写入操作。DataOutput接口用于将数据从任何Java原始类型转换为字节序列,并将这些字节写入二进制流。还提供了一个实用程序,可将字符串转换为UTF-8修改格式并写入生成的字节序列。如果由于某种原因无法写入字节,此接口中所有写入字节的方法都会抛出IOException。ObjectOutputStream和ObjectInputStream类也可以从前面的代码片段中得知。我们一般使用ObjectOutputStream的writeObject方法来持久化一个对象。然后使用ObjectInputStream的readObject从持久化存储中读取对象。关于ObjectInputStream和ObjectOutputStream的更多信息,我会单独写一篇介绍,敬请期待。transient关键字transient关键字的作用是控制变量的序列化。在变量声明前加上这个关键字可以防止变量被序列化到文件中。反序列化后,transient变量的值被设置为初始值,比如int类型为0,object类型为null。transient关键字的扩展也将在下一篇文章中介绍。serializationID虚拟机是否允许反序列化,不仅仅取决于类路径和函数代码是否一致,还有很重要的一点就是两个类的serializationID是否一致(即privatestaticfinallongserialVersionUID).Eclipse中的序列化ID下面提供了两种生成策略,一种是固定的1L,一种是随机生成一个不重复的long类型的数据(实际上是使用JDK工具生成的),这里是一个建议,如果没有特别的需求,就是使用默认的1L就好了,保证代码一致的情况下反序列化成功。那么随机生成的序列化ID有什么作用呢?有时,更改序列化ID可用于限制某些用户的使用。
