序列化

Java 对象只有在虚拟机运行的情况下才存在,而虚拟机关闭了以后,这个对象也随着内存回收被释放掉,这种状态称为“瞬态”。如何把这种瞬态转换为持久态就是序列化要解决的问题。除了持久化需要用到序列化以外,把一个对象在网络上进行传输也是序列化的一个重要功能。在网络上,数据以字节的形式进行传输,序列化可以把一个对象作为整体在网络上传输,在网络的另一端,对这个整体进行还原。这样就实现了以对象为单位的传输。

1如何实现序列化

1.1默认的序列化

Java最简单的序列化可以通过对一个类实现Serializable接口实现。Serializable接口是一个标识接口,并没有定义任何方法。JDK中一些常见的类都实现了这个接口。如果想要对对象进行序列化,这个对象的类必须实现了Serilizable接口,否则将会抛出异常。下面是一个最简单的实例,用于说明如何进行序列化以及反序列化。

(1)定义了一个类  实现了Serializable接口

[java] view plain copy

  1. class Sout implements Serializable
  2. {
  3. public static  int  staticInt=1000;
  4. public int  paramA=1000;
  5. private String  paramB="abc";
  6. }

(2) 序列化与反序列化,序列化使用ObjectOutputStream类的writeObject方法,反序列化使用ObjectInputStream的readObject方法

[java] view plain copy

  1. Sout sout=new Sout();
  2. sout.paramA=2500;
  3. //序列化
  4. ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("d:/a.dat"));
  5. objectOutputStream.writeObject(sout);
  6. objectOutputStream.close();
  7. //反序列化
  8. ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("d:/a.dat"));
  9. Sout sout2=(Sout)
  10. objectInputStream.readObject();

以上两个过程就完成了最简单,也是最常见的序列化方式。这种序列化将会对目标对象的所有属性进行序列化,包括属性是另一个对象的引用的情况,也将对其进行序列化。如果这个引用的对象没有实现Serializable接口,将会抛出异常。这种序列化显然不是最佳的,如果一个目标对象包含很多引用对象,这样的序列化过程将会是十分消耗资源的,而且也是不安全的。

另外还需要注意的是,这种序列化方式只对当前类的属性进行序列化,而不会对它的父类进行序列化,下面的例子将说明这一点

(1) 定义一个Father和一个Son类,子类Son类实现了Serializable接口

[java] view plain copy

  1. class Father
  2. {
  3. public String name;
  4. public int age;
  5. }
  6. class Son extends Father implements Serializable
  7. {
  8. public String sex = "male";
  9. }

(2) 序列化的结果是 属于Father类的属性 name为null

[java] view plain copy

  1. public static void main(String[] args) throws Throwable, IOException
  2. {
  3. Son son = new Son();
  4. son.name = "xiaoming";
  5. son.age = 20;
  6. // 序列化
  7. ObjectOutputStream objectOutputStream = new ObjectOutputStream(
  8. new FileOutputStream("d:/a.dat"));
  9. objectOutputStream.writeObject(son);
  10. objectOutputStream.close();
  11. // 反序列化
  12. ObjectInputStream objectInputStream = new ObjectInputStream(
  13. new FileInputStream("d:/a.dat"));
  14. Son son2 = (Son) objectInputStream.readObject();
  15. System.out.println(son2.sex);
  16. System.out.println(son2.name);
  17. }

对于这种需要序列化父类的情况,它的解决办法之一是让父类实现Serializable接口。

1.2 实现writeObject和readObject方法

这是1.1的升级版,对于1.1中介绍的默认方法来说,它是不可控制的,完全由JDK封装的方法对类进行序列化。这种方式的缺陷不仅仅如上面提到的,而且如果类中的属性有被transient关键字修饰的时候,这种方式可以破坏这种约束。

来看下面的例子

(1)定义一个Person类,其中age字段被transient修饰,表示不可以被序列化。同时定义了writeObject和readObject方法

[java] view plain copy

  1. class Person implements Serializable
  2. {
  3. public transient int age;
  4. public String name;
  5. private void writeObject(ObjectOutputStream out) throws IOException
  6. {
  7. out.defaultWriteObject();
  8. //      out.writeInt(age);
  9. }
  10. private void readObject(ObjectInputStream in) throws IOException,
  11. ClassNotFoundException
  12. {
  13. in.defaultReadObject();
  14. //      age = in.readInt();
  15. }
  16. }

(2) 序列化的结果

如果去掉上面的注释,age字段将同样被序列化。

对于这种方式实现序列化,这其中的defaultWriteObject和defaultReadObject两个方法实际上就是1.1中的默认序列化方法。如果一个类既实现了Serializable接口,又定义了writeObject和readObject方法的话,在序列化时就调用这两个方法进行序列化。而且这两个方法可以在默认的序列化方式基础上进行拓展,即可以实现transient修饰的属性的序列化,又可以对父类的属性进行序列化。

1.3 实现Externalizable接口

这种方式与第二种方式很类似,只不过它实现的Externalizable接口,而且需要实现writeExternal和readExternal这两个方法。这是一种更加随意的序列化的方式,想要序列化那些属性,完成由自己决定。这种方式需要注意一点是,序列化的类需要提供一个空的构造方法。

示例:

[java] view plain copy

  1. class Person implements Externalizable
  2. {
  3. public Person()
  4. {
  5. }
  6. public transient int age;
  7. public String name;
  8. @Override
  9. public void writeExternal(ObjectOutput out) throws IOException
  10. {
  11. out.writeInt(age);
  12. out.writeUTF(name);
  13. }
  14. @Override
  15. public void readExternal(ObjectInput in) throws IOException,
  16. ClassNotFoundException
  17. {
  18. age = in.readInt();
  19. name = in.readUTF();
  20. }
  21. }

注意:

writeObject readObject是私有的,只能被序列化机制调用,与此不同给的是,writeExternal和readExternal是公有的方法,而且还存在允许修改对象现有的状态。

2 对单例的序列化

在对单例的类进行序列化的时候,如果按照上面的方式,那么尽管得到了一致的结果,但并不满足单例的要求。如下:

(1)对于下面的程序,虽然利用序列化产生了新的实例,但是它已经破坏了单例的特性。

[java] view plain copy

  1. SingleLady singleLady=SingleLady.getInstance();
  2. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  3. // 序列化
  4. ObjectOutputStream objectOutputStream = new ObjectOutputStream(
  5. outputStream);
  6. objectOutputStream.writeObject(singleLady);
  7. objectOutputStream.close();
  8. outputStream.toByteArray();
  9. // 反序列化
  10. ObjectInputStream objectInputStream = new ObjectInputStream(
  11. new ByteArrayInputStream(outputStream.toByteArray()));
  12. SingleLady singleLady2 = (SingleLady) objectInputStream.readObject();
  13. System.out.println(singleLady==singleLady2);

[java] view plain copy

  1. class SingleLady implements Serializable {
  2. private static SingleLady singleLady;
  3. private SingleLady() {
  4. }
  5. public static SingleLady getInstance() {
  6. if (singleLady == null) {
  7. singleLady = new SingleLady();
  8. return singleLady;
  9. } else {
  10. return singleLady;
  11. }
  12. }
  13. public String getName() {
  14. return name;
  15. }
  16. public void setName(String name) {
  17. this.name = name;
  18. }
  19. private String name;

(2)这就需要一种特别的方式去实现单例的序列化。需要定义另一种称为readResolve的特殊序列化方法仅需在单例类中定义这个方法。

[java] view plain copy

  1. /**
  2. * 单例的序列化方法
  3. * @return
  4. * @throws ObjectStreamException
  5. */
  6. private Object readResolve() throws ObjectStreamException {
  7. return SingleLady.getInstance();
  8. }

3SerialVersionUID与版本管理

在定义一个类实现了Serializable接口的时候,编译器通常会给出这么一个警告,提示我们需要生成一个 serial version ID。

这个ID到底有什么用?简单的说,它相当于类的版本号,这样它就可以进行反序列化。当一个类进行反序列化的时候,首先要检验SerialVersionUID,如果在反序列化的过程中,SerialVersionUID不匹配的话,这个反序列化的过程就会失败,同时会抛出java.io.InvalidClassException异常,并打印出类的名字以及对应的SerialVersionUID。

当一个类升级的时候,它的所有较新版本都必须把serialVersionUID常量定义为与最初的版本的指纹相同。一旦这个静态成员被置于类中,那么序列化系统就可以读入这个类的不同版本。如果这个类只有方法发生了变化,那么在读入新的对象时不会产生任何问题。但是如果数据域发生了改变,这个时候可能会存在一些问题,对象流也会尽力地把这个对象转换到当前版本上:

  • 对象流会将对象与当前版本进行对比,而且仅仅对比非瞬时和非静态的数据域。如果两个数据域名字相同而类型却不同的话,那么对象流不会进行转换;
  • 如果对象流中的数据域在当前类中不存在,那么对象流将会忽略掉这些数据
  • 如果当前版本存在对象流中不存在的数据,那么这些数据将会被设置成默认值。
  • 如果使用序列化来保存对象,尤其是把一个对象保存到文件中,就需要考虑程序的演化对这个对象的影响。主要的影响就是对属性的增加与删除。

4重复引用的序列化

在实际中我们可能遇到这种情况,两个对象都持有另一个对象的引用,那么在序列化的时候,显然不应该把这个被引用的对象序列化两次。对于这种情况,Java利用了序列号的机制来解决这个问题:

  • 对遇到的每一个引用都关联一个序列号;
  • 每个对象 第一次遇到的时候,保存其对象到数据流中;
  • 如果某个对象已经保存过,那么只写出“与之前保存过的序列号为x的对象相同”。在读对象时,整个过程反过来
  • 对于流中的对象,第一次遇到它的序列号时,构建它,并使用流中数据来初始化它,然后记录这个序列号和新对象之间的关联;
  • 当遇到“与之前保存过的序列号为x的对象相同”标记时,获取与这个顺序号相关联的对象引用;

通过下面的示例可以看出Java是如何实现对重复引用的序列化的

(1) 定义两个类  common和hold

[java] view plain copy

  1. class Common implements Serializable
  2. {
  3. /**
  4. *
  5. */
  6. //private static final long serialVersionUID = -393917310072427835L;
  7. private static int count=0;
  8. private void writeObject(ObjectOutputStream out) throws IOException {
  9. System.out.println( "common write invoked  " +(count++));
  10. out.defaultWriteObject();
  11. }
  12. private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
  13. System.out.println(" common  read invoked");
  14. in.defaultReadObject();
  15. }

[java] view plain copy

  1. class Hold implements Serializable
  2. {
  3. private Common common;
  4. public Common getCommon() {
  5. return common;
  6. }
  7. public void setCommon(Common common) {
  8. this.common = common;
  9. }
  10. }

(2) 序列化比较结果

[java] view plain copy

  1. public static void main(String[] args) throws Throwable {
  2. Common common=new Common();
  3. Hold hold1=new Hold();
  4. Hold hold2=new Hold();
  5. hold1.setCommon(common);
  6. hold2.setCommon(common);
  7. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  8. // 序列化
  9. ObjectOutputStream objectOutputStream = new ObjectOutputStream(
  10. outputStream);
  11. objectOutputStream.writeObject(hold1);
  12. objectOutputStream.writeObject(hold2);
  13. objectOutputStream.close();
  14. outputStream.toByteArray();
  15. // 反序列化
  16. ObjectInputStream objectInputStream = new ObjectInputStream(
  17. new ByteArrayInputStream(outputStream.toByteArray()));
  18. Hold hold3 = (Hold) objectInputStream.readObject();
  19. Hold hold4 = (Hold) objectInputStream.readObject();
  20. System.out.println(hold3.getCommon()==hold4.getCommon());
  21. }

结果显示序列化过程仅仅调用了一次 ,而且反序列化得到的结果也是指向同一个引用的。证明了上面说的Java序列化对于相同引用的机制。

5 clone

在Object类中,定义了protected的Clone方法,当需要进行clone时,这个方法必须被重写。这样很麻烦,而且某种程度上说也不太安全。序列化机制可以巧妙地从另外一个角度去解决这个问题,做法很简单。把一个对象序列化到流,然后进行反序列化。这样就产生了一个深拷贝。

[java] view plain copy

    1. class StudentNew implements Serializable, Cloneable {
    2. public String name;
    3. public ClassMate classMate;
    4. public  StudentNew clone() throws CloneNotSupportedException {
    5. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    6. try {
    7. // 序列化
    8. ObjectOutputStream objectOutputStream = new ObjectOutputStream(
    9. outputStream);
    10. objectOutputStream.writeObject(this);
    11. objectOutputStream.close();
    12. outputStream.toByteArray();
    13. // 反序列化
    14. ObjectInputStream objectInputStream = new ObjectInputStream(
    15. new ByteArrayInputStream(outputStream.toByteArray()));
    16. StudentNew cloneStudent = (StudentNew) objectInputStream.readObject();
    17. return cloneStudent;
    18. } catch (Exception e) {
    19. return null;
    20. }
    21. }
    22. }
时间: 2024-12-29 17:36:32

序列化的相关文章

Day4 - 迭代器&生成器、装饰器、Json & pickle 数据序列化、软件目录结构规范

---恢复内容开始--- 本节内容 迭代器&生成器 装饰器 Json & pickle 数据序列化 软件目录结构规范 作业:ATM项目开发 1.列表生成式,迭代器&生成器 列表生成式 需求:列表a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],要求把列表里的每个值加1 1 a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 2 b = [] 3 for i in a: 4 b.append(i+1) 5 a = b 6 print(a) 普通青

C#中XML与对象之间的序列化、反序列化

using System; using System.IO; using System.Text; using System.Xml; using System.Xml.Serialization; namespace Xml.Utility { public static class XmlUtil { /// <summary> /// 将一个对象序列化为XML字符串 /// </summary> /// <param name="o">要序列化

二叉树的序列化和反序列化

http://blog.csdn.net/qq_27703417/article/details/70958692 先序遍历二叉树,如果遇到空节点,就在str的末尾加上"#!","#"表示这个节点为空,节点值不存在,当然你也可以用其他的特殊字符,"!"表示一个值的结束.如果遇到不为空的节点,假设节点值为3,就在str的末尾加上"3!".现在请你实现树的先序序列化. 先序遍历 import java.util.*; //使用递归

Java复习——I/O与序列化

File类 java.io.File只用于表示文件(目录)的信息(名称.大小等),不能用于文件内容的访问,我们可以通过通过给其构造函数传一个路径来构建以文件,传入的路径名有一个小问题,就是Windows和UNIX 中的路径分隔符斜杠方向的问题:"/" 表示 UNIX 中的根目录,"\" 表示Windows 的根目录.File类有静态的参数可以很简单的解决这个问题:pathSeparator就是其中一个.File类中常用的方法有: exists():测试此抽象路径名表

Java 深拷贝浅拷贝 与 序列化

一.浅拷贝.深拷贝 浅拷贝会对对象中的成员变量进行拷贝:如果是基本类型,拷贝的就是基本类型的值:如果属性是内存地址(引用类型),拷贝的就是内存地址 : 深拷贝,除了基本类型外,引用类型所引用的对象也会进行拷贝:(引用的对象只要求浅拷贝即可:若要深层拷贝,一般可利用序列化和反序列化来实现,也可手动实现各级引用对象的深层拷贝:) 二.实现: 浅拷贝:实现Cloneable接口,重写clone()方法,在clone()调用父类super.clone()即可: 深拷贝:1. 实现Cloneable接口,

PHP中的抽象类与抽象方法/静态属性和静态方法/PHP中的单利模式(单态模式)/串行化与反串行化(序列化与反序列化)/约束类型/魔术方法小结

  前  言  OOP  学习了好久的PHP,今天来总结一下PHP中的抽象类与抽象方法/静态属性和静态方法/PHP中的单利模式(单态模式)/串行化与反串行化(序列化与反序列化). 1  PHP中的抽象类与抽象方法 1.什么是抽象方法?              没有方法体 {} 的方法,必须使用abstract 关键字修饰.这样的方,我们叫做抽象方法.                    abstract function say(); //    抽象方法 2.什么是抽象类?        

序列化与反序列化

对象的序列化,反序列化 1)对象序列化,就是将Object转化为byte序列,反之叫对象的反序列化 2)序列化流(ObjectOutputStream),是过滤流----writeObject() 反序列化流(ObjectInputStream)------readObject() 3)序列化接口(Serializable) 对象必须实现序列化接口,才能进行序列化,否则将出现异常 这个接口,没有任何方法,只是一个标准

NET(C#):XmlArrayItem特性和XmlElement特性在序列化数组的差别

https://www.mgenware.com/blog/?p=142 比如这样一个类,我们用XmlArrayItem特性标明数组内出现的元素类型: public class a{ [XmlArrayItem(Type = typeof(int)), XmlArrayItem(Type = typeof(Guid)), XmlArrayItem(Type = typeof(string))] public object[] arr = new object[] { 12, "hehe"

java 实现序列化的两种方式

序列化是把java对象以字节流的形式写入硬盘或传给网络中的其他计算机. 方式一:实现Serializable借口.该接口是一个空借口,仅用于标识该对象可以被序列化,以便jre对其做封装. 方式二:实现Externalizable借口.该接口继承自Serializable借口,添加了两个方法 writeExternal()/readExternal(),在writeExternal()方法中可以指定需要序列化的属性,实现部分序列化

Flink资料(4) -- 类型抽取和序列化

类型抽取和序列化 本文翻译自Type Extraction and Serialization Flink处理类型的方式比较特殊,包括它自己的类型描述,一般类型抽取和类型序列化框架.该文档描述这些概念并解释其机理. Java API和Scala API处理类型信息的方式有根本性的区别,所以本文描述的问题仅与其中一种API相关 一.Flink中对类型的处理 一般处理类型时,我们并不干涉,而是让编程语言和序列化框架来自动处理类型.与之相反的,Flink想要尽可能掌握进出用户函数的数据类型的信息. 1