Java的序列化与反序列化怎么实现


本篇内容介绍了“Java的序列化与反序列化怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。这里先来一段代码:code 1 创建一个User类,用于序列化及反序列化

packagecom.hollis;importjava.io.Serializable;importjava.util.Date;/**
*Createdbyhollison16/2/2.
*/publicclassUserimplementsSerializable{privateStringname;privateintage;privateDatebirthday;privatetransientStringgender;privatestaticfinallongserialVersionUID=-6849794470754667710L;publicStringgetName(){returnname;
}publicvoidsetName(Stringname){this.name=name;
}publicintgetAge(){returnage;
}publicvoidsetAge(intage){this.age=age;
}publicDategetBirthday(){returnbirthday;
}publicvoidsetBirthday(Datebirthday){this.birthday=birthday;
}publicStringgetGender(){returngender;
}publicvoidsetGender(Stringgender){this.gender=gender;
}@Override
publicStringtoString(){return"User{"+"name='"+name+'''+
",age="+age+
",gender="+gender+
",birthday="+birthday+
'}';
}
}

code 2 对User进行序列化及反序列化的Demo

packagecom.hollis;importorg.apache.commons.io.FileUtils;importorg.apache.commons.io.IOUtils;importjava.io.*;importjava.util.Date;/**
*Createdbyhollison16/2/2.
*/
publicclassSerializableDemo{

publicstaticvoidmain(String[]args){
//InitializesTheObject
Useruser=newUser();
user.setName("hollis");
user.setGender("male");
user.setAge(23);
user.setBirthday(newDate());
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));
UsernewUser=(User)ois.readObject();
System.out.println(newUser);
}catch(IOExceptione){
e.printStackTrace();
}catch(ClassNotFou免费云主机域名ndExceptione){
e.printStackTrace();
}finally{
IOUtils.closeQuietly(ois);
try{
FileUtils.forceDelete(file);
}catch(IOExceptione){
e.printStackTrace();
}
}

}
}
//output
//User{name='hollis',age=23,gender=male,birthday=TueFeb0217:37:38CST2016}
//User{name='hollis',age=23,gender=null,birthday=TueFeb0217:37:38CST2016}

1、在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。2、通过ObjectOutputStreamObjectInputStream对对象进行序列化及反序列化3、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)4、序列化并不保存静态变量。5、要想将父类对象也序列化,就需要让父类也实现Serializable 接口。6、Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。7、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。在介绍ArrayList序列化之前,先来考虑一个问题:如何自定义的序列化和反序列化策略带着这个问题,我们来看java.util.ArrayList的源码code 3

publicclassArrayListextendsAbstractList
implementsList,RandomAccess,Cloneable,java.io.Serializable{privatestaticfinallongserialVersionUID=8683452581122892189L;
transientObject[]elementData;//non-privatetosimplifynestedclassaccess
privateintsize;
}

笔者省略了其他成员变量,从上面的代码中可以知道ArrayList实现了java.io.Serializable接口,那么我们就可以对它进行序列化及反序列化。因为elementData是transient的,所以我们认为这个成员变量不会被序列化而保留下来。我们写一个Demo,验证一下我们的想法:code 4

publicstaticvoidmain(String[]args)throwsIOException,ClassNotFoundException{
ListstringList=newArrayList();
stringList.add("hello");
stringList.add("world");
stringList.add("hollis");
stringList.add("chuang");
System.out.println("initStringList"+stringList);
ObjectOutputStreamobjectOutputStream=newObjectOutputStream(newFileOutputStream("stringlist"));
objectOutputStream.writeObject(stringList);

IOUtils.close(objectOutputStream);
Filefile=newFile("stringlist");
ObjectInputStreamobjectInputStream=newObjectInputStream(newFileInputStream(file));
ListnewStringList=(List)objectInputStream.readObject();
IOUtils.close(objectInputStream);
if(file.exists()){
file.delete();
}
System.out.println("newStringList"+newStringList);
}
//initStringList[hello,world,hollis,chuang]//newStringList[hello,world,hollis,chuang]

了解ArrayList的人都知道,ArrayList底层是通过数组实现的。那么数组elementData其实就是用来保存列表中的元素的。通过该属性的声明方式我们知道,他是无法通过序列化持久化下来的。那么为什么code 4的结果却通过序列化和反序列化把List中的元素保留下来了呢?在ArrayList中定义了来个方法: writeObjectreadObject。这里先给出结论:在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。来看一下这两个方法的具体实现:code 5

privatevoidreadObject(java.io.ObjectInputStreams)
throwsjava.io.IOException,ClassNotFoundException{
elementData=EMPTY_ELEMENTDATA;//Readinsize,andanyhiddenstuff
s.defaultReadObject();//Readincapacity
s.readInt();//ignored

if(size>0){//belikeclone(),allocatearraybaseduponsizenotcapacity
ensureCapacityInternal(size);Object[]a=elementData;//Readinallelementsintheproperorder.
for(inti=0;i

code 6

privatevoidwriteObject(java.io.ObjectOutputStreams)
throwsjava.io.IOException{//Writeoutelementcount,andanyhiddenstuff
intexpectedModCount=modCount;
s.defaultWriteObject();//Writeoutsizeascapacityforbehaviouralcompatibilitywithclone()
s.writeInt(size);//Writeoutallelementsintheproperorder.
for(inti=0;i

那么为什么ArrayList要用这种方式来实现序列化呢?ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。前面说过,为了防止一个包含大量空对象的数组被序列化,为了优化存储,所以,ArrayList使用transient来声明elementData。 但是,作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写writeObjectreadObject方法的方式把其中的元素保留下来。writeObject方法把elementData数组中的元素遍历的保存到输出流(ObjectOutputStream)中。readObject方法从输入流(ObjectInputStream)中读出对象并保存赋值到elementData数组中。至此,我们先试着来回答刚刚提出的问题:如何自定义的序列化和反序列化策略答:可以通过在被序列化的类中增加writeObject 和 readObject方法。那么问题又来了:虽然ArrayList中写了writeObject 和 readObject 方法,但是这两个方法并没有显示的被调用啊。那么如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的呢?从code 4中,我们可以看出,对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的,那么带着刚刚的问题,我们来分析一下ArrayList中的writeObject 和 readObject 方法到底是如何被调用的呢?为了节省篇幅,这里给出ObjectOutputStream的writeObject的调用栈:writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject这里看一下invokeWriteObject:

voidinvokeWriteObject(Objectobj,ObjectOutputStreamout)
throwsIOException,UnsupportedOperationException
{if(writeObjectMethod!=null){try{
writeObjectMethod.invoke(obj,newObject[]{out});
}catch(InvocationTargetExceptionex){Throwableth=ex.getTargetException();if(thinstanceofIOException){throw(IOException)th;
}else{
throwMiscException(th);
}
}catch(IllegalAccessExceptionex){//shouldnotoccur,asaccesscheckshavebeensuppressed
thrownewInternalError(ex);
}
}else{thrownewUnsupportedOperationException();
}
}

其中writeObjectMethod.invoke(obj, new Object[]{ out });是关键,通过反射的方式调用writeObjectMethod方法。官方是这么解释这个writeObjectMethod的:class-defined writeObject method, or null if none在我们的例子中,这个方法就是我们在ArrayList中定义的writeObject方法。通过反射的方式被调用了。至此,我们先试着来回答刚刚提出的问题:如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的?答:在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法时,会通过反射的方式调用。至此,我们已经介绍完了ArrayList的序列化方式。那么,不知道有没有人提出这样的疑问:Serializable明明就是一个空的接口,它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢?Serializable接口的定义:

publicinterfaceSerializable{
}

读者可以尝试把code 1中的继承Serializable的代码去掉,再执行code 2,会抛出java.io.NotSerializableException。其实这个问题也很好回答,我们再回到刚刚ObjectOutputStream的writeObject的调用栈:writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObjectwriteObject0方法中有这么一段代码:

if(objinstanceofString){writeString((String)obj,unshared);
}elseif(cl.isArray()){writeArray(obj,desc,unshared);
}elseif(objinstanceofEnum){writeEnum((Enum>)obj,desc,unshared);
}elseif(objinstanceofSerializable){writeOrdinaryObject(obj,desc,unshared);
}else{
if(extendedDebugInfo){
thrownewNotSerializableException(
cl.getName()+"n"+debugInfoStack.toString());
}else{
thrownewNotSerializableException(cl.getName());
}
}

在进行序列化操作时,会判断要被序列化的类是否是Enum、Array和Serializable类型,如果不是则直接抛出NotSerializableException。“Java的序列化与反序列化怎么实现”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注百云主机网站,小编将为大家输出更多高质量的实用文章!

相关推荐: java File的常用方法有哪些

本篇内容主要讲解“java File的常用方法有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“java File的常用方法有哪些”吧!1.凡是与输入输出相关的类,接口等都定义在java.io包下2.File是一…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

Like (0)
Donate 微信扫一扫 微信扫一扫
Previous 02/21 13:00
Next 02/21 13:00

相关推荐