java序列化与反序列化_第1页
java序列化与反序列化_第2页
java序列化与反序列化_第3页
java序列化与反序列化_第4页
java序列化与反序列化_第5页
已阅读5页,还剩8页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、java序列化与反序列化一、什么是序列化?序列化是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是一种将Java对象的状态转换为字节数组,以便存储或传输的机制,以后,仍可以将字节数组转换回Java对象原有的状态。序列化的思想是 “冻结” 对象状态,传输对象状态(写到磁盘、通过网络传输等等),然后“解冻”状态,重新获得可用的Java对象。所有这些事情的发生有点像是魔术,这要归功于ObjectInputStream/ObjectOutputStream类、完全保真的元数据,以及程序员愿意用Serializab

2、le标识接口标记他们的类,从而 “参与” 这个过程。把对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为对象的过程称为对象的反序列化。二、为什么要序列化?什么情况下需要序列化?当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个对象转换为字节序列,才能在网络上传送,接收方则需要把字节序列再恢复为对象。对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。 java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对

3、象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的"深复制",即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。序列化能将对象转成如XML/Bit流,方便对象的磁盘存储、网络传输(webService,RMI)。对象的寿命常随着生成该对象的程序的终止而终止。有时候,可能需要将对象的状态保存下来,在需要时再将对象恢复以延续其状态。如何做到程序终止后对象继续存在? 如何将对象在网络上传输? 三、相关接口和类(1)Serializable接口和Externalizable接口(2)ObjectOutput接口:它继承DataOu

4、tput接口并且支持对象的序列化,其内的writeObject()方法实现存储一个对象。(3)ObjectOutputStream类:它继承OutputStream类并且实现ObjectOutput接口。利用该类来实现将对象存储(调用ObjectOutput接口中的writeObject()方法)。(4)ObjectInput接口:它继承DataInput接口并且支持对象的序列化,其内的readObject()方法实现读取一个对象。(5)ObjectInputStream类:它继承InputStream类并且实现ObjectInput接口。利用该类来实现将对象存储(调用ObjectInput接

5、口中的readObject()方法)。四、怎么实现序列化?要被序列化的类必须实现Serializable接口或者Externalizable接口之一。1. 实现Serializable接口,不需要实现任何方法,只是一个标记接口。package com.test.ser;import java.io.Serializable;public class Person implements Serializable/* * serial version id */private static final long serialVersionUID = -2858407989098901728L;pu

6、blic Person(String name,int age) = name;this.age = age;private String name;private int age;public String getName() return name;public void setName(String name) = name;public int getAge() return age;public void setAge(int age) this.age = age;public String toString()return "Per

7、son:name="+name+"age="+age+""2. 使用java.io.ObjectOutputStream类的writeObject()方法,将对象保存到磁盘文件,文件后缀名可以任意起。使用java.io.ObjectInputStream类的readObject()方法,将对象反序列化为原始对象。如果同时序列化多个对象到一个序列化文件时,读取的顺序应与写入的顺序保持一致。package com.test.ser;import java.io.FileInputStream;import java.io.FileNotFoundE

8、xception;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class SerTest public static void main(String args) /可序列化基本类型、普通

9、对象为文件 Person p1 = new Person("张三",24);Person p2 = new Person("李四",25);/也可序列化容器对象为文件 List<Person> ps = new ArrayList<Person>();ps.add(p1);ps.add(p2);/也可序列化复杂容器对象为文件 Map<String,List<Person>> map = new HashMap<String,List<Person>>();map.put("

10、m", ps);/序列化到磁盘文件try FileOutputStream fos = new FileOutputStream("tempdata.ser");/文件输出流ObjectOutputStream oos = new ObjectOutputStream(fos); /也可使用其他输出流构造ObjectOutputStream,如ByteArrayOutputStream等oos.writeObject(ps);/写入第一个对象oos.writeObject(1111);/写入第二个对象fos.close();oos.close(); catch (

11、FileNotFoundException e) / TODO Auto-generated catch blocke.printStackTrace(); catch (IOException e) / TODO Auto-generated catch blocke.printStackTrace();/从文件反序列化为原始对象try FileInputStream fis = new FileInputStream("tempdata.ser");ObjectInputStream ois = new ObjectInputStream(fis);SuppressWa

12、rnings("unchecked")List<Person> plist = (List<Person>)ois.readObject();/读取第一个对象,读取的顺序应与写入的顺序保持一致for(Person p:plist)System.out.println("name:"+p.getName()+" age:"+p.getAge();Integer i = (Integer)ois.readObject();/读取第二个对象System.out.println(Value();/new Fi

13、le("tempdata.ser").delete(); catch (FileNotFoundException e) / TODO Auto-generated catch blocke.printStackTrace(); catch (IOException e) / TODO Auto-generated catch blocke.printStackTrace(); catch (ClassNotFoundException e) / TODO Auto-generated catch blocke.printStackTrace();五、序列化的特性?1.什么

14、被序列化属性(包括基本数据类型、数组、对其它对象的引用(深度复制))类名2.什么不被序列化static的属性方法加了transient修饰符的属性3.序列化特点(1)如果某个类能够被序列化,其子类也可以被序列化。(2)声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,不是对象的状态,transient代表对象的临时数据。(3)属性中对其他类对象的引用时,引用的类也必须实现序列化,因为序列化是深度序列化,如果不确定引用类是否实现了序列化接口,则最好声明此引用属性不被序列化。(4)一个子类实现了 Serializable 接口,它的父类都没有实现 Se

15、rializable 接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。只有当父类也实现Serializable接口时,父类对象才会被序列化(先有父对象,才有子对象),如果父类没实现序列化,那么父类对象不会被序列化,当被反序列化时,会自动调用其无参构造函数来构造父类对象,所以如果父类没有实现Serializable接口,就必须有无参的构造方法。要想将父类对象也序列化,就需要让父类也实现Serializable 接口。如果父类不实现的话的,就 需要有默认的无参的构造函数。在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,

16、而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。(5)序列化接口产生的序列化ID:在分布式应用中,如果需要将对象通过序列化进行传输,那么就有一个问题,那就是一端进行序列化,另一端就必须反序列化,那么是不是两个类的代码一摸一样就一定能强制转换成功呢,其实不是的,序

17、列化类有个序列化ID,两个类完全相同,除了代码相同,同时这个ID也需要相同,那么强制转换就成功了。(6)序列化存储规则看下面代码: ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.obj");Test test = new Test();/试图将对象两次写入文件out.writeObject(test);out.flush();System.out.println(new File("result.obj").length();out.writeOb

18、ject(test);out.close();System.out.println(new File("result.obj").length();ObjectInputStream oin = new ObjectInputStream(new FileInputStream("result.obj");/从文件依次读出两个文件Test t1 = (Test) oin.readObject();Test t2 = (Test) oin.readObject();oin.close();/判断两个引用是否指向同一个对象System.out.println

19、(t1 = t2);对同一对象两次写入文件,打印出写入一次对象后的存储大小和写入两次后的存储大小,然后从文件中反序列化出两个对象,比较这两个对象是否为同一对象。一般的思维是,两次写入对象,文件大小会变为两倍的大小,反序列化时,由于从文件读取,生成了两个对象,判断相等时应该是输入 false 才对,但是最后结果输出序列化文件只增加了5b(字节),而且比较结果为true,两个对象是相等的,这是因为Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反

20、序列化时,恢复引用关系,使得清单 3 中的 t1 和 t2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。再看下面代码:ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.obj");Test test = new Test();test.i = 1;out.writeObject(test);out.flush();test.i = 2;out.writeObject(test);out.close();ObjectInputStream oi

21、n = new ObjectInputStream(new FileInputStream("result.obj");Test t1 = (Test) oin.readObject();Test t2 = (Test) oin.readObject();System.out.println(t1.i);System.out.printlni);上述代码的目的是希望将 test 对象两次保存到 result.obj 文件中,写入一次以后修改对象属性值再次保存第二次,然后从 result.obj 中再依次读出两个对象,输出这两个对象的 i 属性值。案例代码的目的原本是希望一次

22、性传输对象修改前后的状态。结果两个输出的都是 1, 原因就是第一次写入对象以后,第二次再试图写的时候,虚拟机根据引用关系知道已经有一个相同对象已经写入文件,因此只保存第二次写的引用,所以读取时,都是第一次保存的对象。在使用一个文件多次 writeObject 需要特别注意这个问题。(7)当要序列化的类也重写了writeReplace和readResolve方法时,writeReplace会在writeObject前执行,readResolve会在readObject后执行。writeReplace invokedwriteObject(External) invokedreadObject(E

23、xternal) invokedreadResolve invoked首先JVM会先调用writeReplace方法,在这个阶段,我们可以进行张冠李戴,将需要进行序列化的对象换成我们指定的对象.跟着JVM将调用writeObject方法,来将对象中的属性一个个进行序列化,我们可以在这个方法中控制住哪些属性需要序列化.当反序列化的时候:JVM会调用readObject方法,将我们刚刚在writeObject方法序列化好的属性,反序列化回来.然后在readResolve方法中,我们也可以指定JVM返回我们特定的对象(不是刚刚序列化回来的对象).注意到在writeReplace和readResolv

24、e,我们可以严格控制singleton的对象,在同一个JVM中完完全全只有唯一的对象,控制不让singleton对象产生副本.六、类的某个属性(如密码)不想被暴露或不想被序列化怎么办?1. 定制对象序列化,重写writeObject和readObject方法,对某些属性进行处理,如加密、签名等。package com.test.ser2;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public c

25、lass Person implements Serializable/* * serial version id */private static final long serialVersionUID = -2858407989098901728L;public Person(String name,int age,String psw) = name;this.age = age;this.pwd = psw;private String name;private int age;private String pwd;public String getName() re

26、turn name;public void setName(String name) = name;public int getAge() return age;public void setAge(int age) this.age = age;public String getPwd() return pwd;public void setPwd(String pwd) this.pwd = pwd;public String toString()return "Person:name="+name+"age="+age+&quo

27、t;"/当这个类被序列化时writeObject被自动调用,不用显示调用,所以声明为privateprivate void writeObject(ObjectOutputStream out)String password = encrypt(pwd);/密码被加密处理,然后再序列化try out.writeObject(name);out.writeInt(age);out.writeObject(password); catch (IOException e) / TODO Auto-generated catch blocke.printStackTrace();/readO

28、bject被自动调用,不用显示调用,所以声明为privateprivate void readObject(ObjectInputStream in)try = (String)in.readObject();this.age = in.readInt();this.pwd = new StringBuilder(String)in.readObject().reverse().toString();/解密 catch (IOException e) / TODO Auto-generated catch blocke.printStackTrace(); catch (C

29、lassNotFoundException e) / TODO Auto-generated catch blocke.printStackTrace();public String encrypt(String psw)return new StringBuilder(psw).reverse().toString();2.使用transient关键字,(transient是“临时的”),如下:可使pwd属性不被序列化。private transient String pwd;Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反

30、序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。3.实现Externalizable接口参见七。七、Externalizable(外部化)怎么用?Externalizable接口表示实现该接口的类在序列化中由该类本身来控制信息的写出和读入。(1)实现Externalizable接口(2)实现readExternal()/writeExternal()方法,定义要序列化的属性(3)需要一个不带参数的构造器(是否需要显式定义根据类是否有定义其他构造器而定)package com.test.ser3;import java.io.Externaliz

31、able;import java.io.IOException;import java.io.ObjectInput;import java.io.ObjectOutput;public class Person implements Externalizable/* * serial version id */private static final long serialVersionUID = -2858407989098901728L;/使用Externalizable时,必须有一个无参构造器public Person()public Person(String name,int ag

32、e,String psw) = name;this.age = age;this.pwd = psw;private String name;private int age;private String pwd;public String getName() return name;public void setName(String name) = name;public int getAge() return age;public void setAge(int age) this.age = age;public String getPwd() re

33、turn pwd;public void setPwd(String pwd) this.pwd = pwd;public String toString()return "Person:name="+name+"age="+age+""Overridepublic void writeExternal(ObjectOutput out) throws IOException / TODO Auto-generated method stubout.writeObject(name);out.writeInt(age);Overrid

34、epublic void readExternal(ObjectInput in) throws IOException,ClassNotFoundException / TODO Auto-generated method = (String)in.readObject();this.age = in.readInt();八、Serializable和Externalizable有什么异同?(1)Externalizable接口继承自Serializable接口public interface Serializable public interface Exter

35、nalizable extends Serializable void readExternal(ObjectInput in); void writeExternal(ObjectOutput out); (2)外部化和序列化是实现同一目标的两种不同方法。通过Serializable接口对对象序列化的支持是内建于核心 API 的,但是java.io.Externalizable的所有实现者必须提供读取和写出的实现。Java 已经具有了对序列化的内建支持,也就是说只要制作自己的类java.io.Serializable,Java 就会试图存储和重组你的对象。如果使用外部化,你就可以选择完全由自

36、己完成读取和写出的工作,Java 对外部化所提供的唯一支持是接口: voidreadExternal(ObjectInput in) void writeExternal(ObjectOutput out) 现在如何实现readExternal() 和writeExternal() 就完全看你自己了。 序列化会自动存储必要的信息,用以反序列化被存储的实例,而外部化则只保存被存储的类的标识。当你通过java.io.Serializable接口序列化一个对象时,有关类的信息,比如它的属性和这些属性的类型,都与实例数据一起被存储起来。在选择走Externalizable这条路时,Java 只存储有关

37、每个被存储类型的非常少的信息。 每个接口的优点和缺点 Serializable接口 · 优点:内建支持 · 优点:易于实现 · 缺点:占用空间过大 · 缺点:由于额外的开销导致速度变比较慢 Externalizable接口 · 优点:开销较少(程序员决定存储什么) · 优点:可能的速度提升 · 缺点:虚拟机不提供任何帮助,也就是说所有的工作都落到了开发人员的肩上。 在两者之间如何选择要根据应用程序的需求来定。Serializable通常是最简单的解决方案,但是它可能会导致出现不可接受的性能问题或空间问题;在出现这些问题的情况

38、下,Externalizable可能是一条可行之路。 要记住一点,如果一个类是可外部化的(Externalizable),那么Externalizable方法将被用于序列化类的实例,即使这个类型提供了Serializable方法: private void writeObject() private void readObject()九、怎么序列化为xml文件?使用XStream(java对象与Xml转换),下载地址:/引入xstream core only-1.4.1.jarxmlpull-.jarxpp3_min-1.1.4c.zip 等jar包,下载地址:/download.html(XStream支持一下几种xml解析,官方推荐XmlPull、Xpp3具体Xml格式及XStream知识请查相关资料!package com.test.ser4;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import c

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论