文章出自:http://www.cnblogs.com/chenfei0801/archive/2013/04/05/3001149.html

Java的对象序列化是指将那些实现了Serializable接口的对象转换成一个字符序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。 只要对象实现了Serializable接口(记住,这个接口只是一个标记接口,不包含任何的方法

如果我们想要序列化一个对象,首先要创建某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中。这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(记住:对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。而饭序列的过程(即将一个序列还原成为一个对象),需要将一个InputStream(如FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()即可。

对象序列化过程不仅仅保存单个对象,还能追踪对象内所包含的所有引用,并保存那些对象(这些对象也需实现了Serializable接口)。下面这段代码演示了此过程:

  1. package test.serializable;
  2.  
  3. /**
  4. *@chenfeic
  5. *
  6. *只是一个简单的类,用于测试序列化
  7. */
  8. import java.io.Serializable;
  9.  
  10. public class Data implements Serializable {
  11. private static final long serialVersionUID = 7247714666080613254L;
  12. public int n;
  13. public Data(int n) {
  14. this.n = n;
  15. }
  16. public String toString(){
  17. return Integer.toString(n);
  18. }
  19. }
  1. package test.serializable;
  2.  
  3. import java.io.Serializable;
  4. import java.util.Random;
  5.  
  6. /**
  7. *
  8. * @author chenfei
  9. *
  10. * 用于测试序列化,每个对象Worm对象都与worm中的下一段链接,同时又有属于不同类(Data)的对象引用数组链接
  11. */
  12. public class Worm implements Serializable {
  13. private static final long serialVersionUID = 5468335797443850679L;
  14. private Data[] d = {
  15. new Data(random.nextInt(10)),
  16. new Data(random.nextInt(10)),
  17. new Data(random.nextInt(10))
  18. };
  19. private static Random random = new Random(47);
  20. private Worm next;
  21. private char c;
  22.  
  23. public Worm(int i , char x) {
  24. System.out.println("Worm constructor:" +i);
  25. c = x;
  26. if(--i > 0) {
  27. next = new Worm(i , (char)(x+1));
  28. }
  29. }
  30. public Worm() {
  31. System.out.println("Default constructor!");
  32. }
  33.  
  34. public String toString() {
  35. StringBuilder sb = new StringBuilder(":");
  36. sb.append(c);
  37. sb.append("(");
  38. for(Data data : d) {
  39. sb.append(data);
  40. }
  41. sb.append(")");
  42. if(next!=null) {
  43. sb.append(next);
  44. }
  45. return sb.toString();
  46. }
  47. }
  1. package test.serializable;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.ByteArrayOutputStream;
  4. import java.io.FileInputStream;
  5. import java.io.FileNotFoundException;
  6. import java.io.FileOutputStream;
  7. import java.io.IOException;
  8. import java.io.ObjectInputStream;
  9. import java.io.ObjectOutputStream;
  10.  
  11. public class SerializableTest {
  12.  
  13. public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
  14. Worm w = new Worm(6 ,'a');
  15. System.out.println("序列化操纵之前");
  16. System.out.println("w="+w);
  17.  
  18. //序列化操作1--FileOutputStream
  19. ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("worm.out"));
  20. oos1.writeObject("Worm storage By FileOutputStream ");
  21. oos1.writeObject(w);//必须所有引用的对象都实现序列化(本例终究是Data这个类),否则抛出有java.io.NotSerializableException:这个异常
  22. oos1.close();
  23.  
  24. //反序列化操作1---FileInputStream
  25. ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream("worm.out"));
  26. String s1 = (String)ois1.readObject();
  27. Worm w1 = (Worm)ois1.readObject();
  28. ois1.close();
  29. System.out.println("反序列化操作1之后");
  30. System.out.println(s1);
  31. System.out.println("w1:"+w1);
  32.  
  33. //序列化操作2--ByteArrayOutputStream
  34. ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
  35. ObjectOutputStream oos2 = new ObjectOutputStream(byteOutStream);
  36. oos2.writeObject("Worm storage By ByteOutputStream ");
  37. oos2.writeObject(w);
  38. oos2.flush();
  39.  
  40. //反序列操作2--ByteArrayInputStream
  41. ByteArrayInputStream byteInStream = new ByteArrayInputStream(byteOutStream.toByteArray());
  42. ObjectInputStream ois2 = new ObjectInputStream(byteInStream);
  43. String s2 = (String)ois2.readObject();
  44. Worm w2 = (Worm)ois2.readObject();
  45. ois2.close();
  46. System.out.println("反序列化操作2之后");
  47. System.out.println(s2);
  48. System.out.println("w2:"+w2);
  49. }
  50.  
  51. }

运行的结果如下:

  1. Worm constructor:6
  2. Worm constructor:5
  3. Worm constructor:4
  4. Worm constructor:3
  5. Worm constructor:2
  6. Worm constructor:1
  7. 序列化操纵之前
  8. w=:a(853):b(119):c(802):d(788):e(199):f(881)
  9. 反序列化操作1之后
  10. Worm storage By FileOutputStream
  11. w1::a(853):b(119):c(802):d(788):e(199):f(881)
  12. 反序列化操作2之后
  13. Worm storage By ByteOutputStream
  14. w2::a(853):b(119):c(802):d(788):e(199):f(881)

思考:

1)反序列化后的对象,需要调用构造函数重新构造吗?

答案:不需要。对于Serializable对象,对象完全以它存储的二进制位作为基础来构造,而不调用构造器。

请看下面这段代码

  1. package test.serializable;
  2.  
  3. import java.io.Serializable;
  4. import java.util.Date;
  5.  
  6. /**
  7. *
  8. * @author chenfei
  9. *
  10. * 用于测试序列化时的deep copy
  11. *
  12. */
  13. public class House implements Serializable {
  14. private static final long serialVersionUID = -6091530420906090649L;
  15.  
  16. private Date date = new Date(); //记录当前的时间
  17.  
  18. public String toString() {
  19. return "House:" + super.toString() + ".Create Time is:" + date;
  20. }
  21.  
  22. }
  1. package test.serializable;
  2.  
  3. import java.io.Serializable;
  4.  
  5. public class Animal implements Serializable {
  6. private static final long serialVersionUID = -213221189192962074L;
  7.  
  8. private String name;
  9.  
  10. private House house;
  11.  
  12. public Animal(String name , House house) {
  13. this.name = name;
  14. this.house = house;
  15. System.out.println("调用了构造器");
  16. }
  17.  
  18. public String toString() {
  19. return name + "[" +super.toString() + "']" + house;
  20. }
  21.  
  22. }
  1. package test.serializable;
  2.  
  3. import java.io.ByteArrayInputStream;
  4. import java.io.ByteArrayOutputStream;
  5. import java.io.IOException;
  6. import java.io.ObjectInputStream;
  7. import java.io.ObjectOutputStream;
  8.  
  9. public class Myworld {
  10.  
  11. /**
  12. * @param args
  13. * @throws IOException
  14. * @throws ClassNotFoundException
  15. */
  16. public static void main(String[] args) throws IOException, ClassNotFoundException {
  17. House house = new House();
  18. System.out.println("序列化前");
  19. Animal animal = new Animal("test",house);
  20. ByteArrayOutputStream out = new ByteArrayOutputStream();
  21. ObjectOutputStream oos = new ObjectOutputStream(out);
  22. oos.writeObject(animal);
  23. oos.flush();
  24. oos.close();
  25.  
  26. System.out.println("反序列化后");
  27. ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
  28. ObjectInputStream ois = new ObjectInputStream(in);
  29. Animal animal1 = (Animal)ois.readObject();
  30. ois.close();
  31. }
  32.  
  33. }

    运行结果如下所示:

  1. 序列化前
  2. 调用了构造器
  3. 反序列化后

从上面的结果中可以看到,在序列化前,当我们使用

  1. Animal animal = new Animal("test",house);

时,调用了Animal的构造器(打印了输出语句),但是反序列后并没有再打印任何语句,说明并没有调用构造器。

2)序列前的对象与序列化后的对象是什么关系?是("=="还是equal?是浅复制还是深复制?)

答案:深复制,反序列化还原后的对象地址与原来的的地址不同。 我们还是看上面思考1)中给出的代码,前两个类不变化,修改第三个类(MyWorld.java)的部分代码,修改后的代码如下:

  1. package test.serializable;
  2.  
  3. import java.io.ByteArrayInputStream;
  4. import java.io.ByteArrayOutputStream;
  5. import java.io.IOException;
  6. import java.io.ObjectInputStream;
  7. import java.io.ObjectOutputStream;
  8.  
  9. public class Myworld {
  10.  
  11. /**
  12. * @param args
  13. * @throws IOException
  14. * @throws ClassNotFoundException
  15. */
  16. public static void main(String[] args) throws IOException, ClassNotFoundException {
  17. House house = new House();
  18. System.out.println("序列化前");
  19. Animal animal = new Animal("test",house);
  20. System.out.println(animal);
  21. ByteArrayOutputStream out = new ByteArrayOutputStream();
  22. ObjectOutputStream oos = new ObjectOutputStream(out);
  23. oos.writeObject(animal);
  24. oos.writeObject(animal);//在写一次,看对象是否是一样,
  25. oos.flush();
  26. oos.close();
  27.  
  28. ByteArrayOutputStream out2 = new ByteArrayOutputStream();//换一个输出流
  29. ObjectOutputStream oos2 = new ObjectOutputStream(out2);
  30. oos2.writeObject(animal);
  31. oos2.flush();
  32. oos2.close();
  33.  
  34. System.out.println("反序列化后");
  35. ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
  36. ObjectInputStream ois = new ObjectInputStream(in);
  37. Animal animal1 = (Animal)ois.readObject();
  38. Animal animal2 = (Animal)ois.readObject();
  39. ois.close();
  40.  
  41. ByteArrayInputStream in2 = new ByteArrayInputStream(out2.toByteArray());
  42. ObjectInputStream ois2 = new ObjectInputStream(in2);
  43. Animal animal3 = (Animal)ois2.readObject();
  44. ois2.close();
  45.  
  46. System.out.println("out流:" +animal1);
  47. System.out.println("out流:" +animal2);
  48. System.out.println("out2流:" +animal3);
  49.  
  50. System.out.println("测试序列化前后的对象 == :"+ (animal==animal1));
  51. System.out.println("测试序列化后同一流的对象:"+ (animal1 == animal2));
  52. System.out.println("测试序列化后不同流的对象==:" + (animal1==animal3));
  53.  
  54. }
  55.  
  56. }

运行结果如下:

  1. 序列化前
  2. 调用了构造器
  3. test[test.serializable.Animal@bb7465']House:test.serializable.House@d6c16c.Create Time is:Sat Apr 06 00:11:30 CST 2013
  4. 反序列化后
  5. out流:test[test.serializable.Animal@4f80d6']House:test.serializable.House@193722c.Create Time is:Sat Apr 06 00:11:30 CST 2013
  6. out流:test[test.serializable.Animal@4f80d6']House:test.serializable.House@193722c.Create Time is:Sat Apr 06 00:11:30 CST 2013(与上面的相同)
  7. out2流:test[test.serializable.Animal@12cc95d']House:test.serializable.House@157fb52.Create Time is:Sat Apr 06 00:11:30 CST 2013(与上面只是值相同,但是地址不一样。)
  8. 测试序列化前后的对象 == false
  9. 测试序列化后同一流的对象:true
  10. 测试序列化后不同流的对象==:false

从结果可以看到

序列化前后对象的地址不同了,但是内容是一样的,而且对象中包含的引用也相同。换句话说,通过序列化操作,我们可以实现对任何可Serializable对象的”深度复制(deep copy)"——这意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。对于同一流的对象,他们的地址是相同,说明他们是同一个对象,但是与其他流的对象地址却不相同。也就说,只要将对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,而且只要在同一流中,对象都是同一个。

补充:

serialVersionUID 的作用?

在Java中,软件的兼容性是一个大问题,尤其在使用到对象串行性的时候,那么在某一个对象已经被串行化了,可是这个对象又被修改后重新部署了,那么在这种情况下, 用老软件来读取新文件格式虽然不是什么难事,但是有可能丢失一些信息。 serialVersionUID来解决这些问题,新增的serialVersionUID必须定义成下面这种形式:static final long serialVersionUID=-2805284943658356093L;。其中数字后面加上的L表示这是一个long值。 通过这种方式来解决不同的版本之间的串行话问题。

Java串行化机制定义的文件格式似乎很脆弱,只要稍微改动一下类的定义,原来保存的对象就可能无法读取。例如,下面是一个简单的类定义:

  1. public class Save implements Serializable
  2. {
  3. String name;
  4.  
  5. public void save() throws IOException
  6. {
  7. FileOutputStream f = new FileOutputStream("foo");
  8. ObjectOutputStream oos = new ObjectOutputStream(f);
  9. oos.writeObject(this);
  10. oos.close();
  11. }
  12. }

如果在这个类定义中增加一个域,例如final int val = 7;,再来读取原来保存的对象,就会出现下面的异常:

java.io.InvalidClassException:
Save; local class incompatible:
stream classdesc serialVersionUID = -2805284943658356093,
local class serialVersionUID = 3419534311899376629

上例异常信息中的数字串表示类定义里各种属性的编码值:

●类的名字(Save)。

●域的名字(name)。

●方法的名字(Save)。

●已实现的接口(Serializable)。

改动上述任意一项内容(无论是增加或删除),都会引起编码值变化,从而引起类似的异常警报。这个数字序列称为“串行化版本统一标识符”(serial
version universal
identifier),简称UID。解决这个问题的办法是在类里面新增一个域serialVersionUID,强制类仍旧使用原来的UID。
新增的域必须是:

●static:该域定义的属性作用于整个类,而非特定的对象。

●final:保证代码运行期间该域不会被修改。

●long:它是一个64位的数值。

也就是说,新增的serialVersionUID必须定义成下面这种形式:static final long serialVersionUID=-2805284943658356093L;。其中数字后面加上的L表示这是一个long值。

当然,改动之后的类不一定能够和原来的对象兼容。例如,如果把一个域的定义从String改成了int,执行逆-串行化操作时系统就不知道如何处理该值,显示出错误信息:java.io.InvalidClassException:
Save; incompatible types for field name。

JAVA 对象序列化——Serializable(转)的更多相关文章

  1. JAVA 对象序列化——Serializable

    1.序列化是干什么的?       简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来.虽然你可以用你自己的各种各样的方法来保存object st ...

  2. Java对象序列化剖析

    对象序列化的目的 1)希望将Java对象持久化在文件中 2)将Java对象用于网络传输 实现方式 如果希望一个类的对象可以被序列化/反序列化,那该类必须实现java.io.Serializable接口 ...

  3. 理解Java对象序列化

    http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html 1. 什么是Java对象序列化 Java平台允许我们在内存中创 ...

  4. java 对象序列化与反序列化

    Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化  Java序列化是指把Java对象转换为 ...

  5. java 对象序列化

    java 对象序列化 package org.rui.io.serializable; import java.io.ByteArrayInputStream; import java.io.Byte ...

  6. 理解Java对象序列化(二)

    关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制.在撰写本文时,既参考了Th ...

  7. 关于 Java 对象序列化您不知道的 5 件事

    数年前,当和一个软件团队一起用 Java 语言编写一个应用程序时,我体会到比一般程序员多知道一点关于 Java 对象序列化的知识所带来的好处. 关于本系列 您觉得自己懂 Java 编程?事实上,大多数 ...

  8. java 对象序列化 RMI

    对于一个存在于Java虚拟机中的对象来说,其内部的状态只保持在内存中.JVM停止之后,这些状态就丢失了.在很多情况下,对象的内部状态是需要被持久化下来的.提到持久化,最直接的做法是保存到文件系统或是数 ...

  9. Java对象序列化入门

      Java对象序列化入门 关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制 ...

随机推荐

  1. python笔记2-数据类型:元组、字典常用操作

    元组 Python的元组与列表类似,不同之处在于元组的元素不能修改. 元组使用小括号,列表使用方括号. 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可. tp=(1,2,3,'a','b' ...

  2. 从头认识java-17.4 具体解释同步(2)-具体解释竞争条件

    这一章节我们来具体讨论一下竞争条件. 1.为什么会引起竞争条件? 因为操作缺失原子性. 2.什么是原子性? 所谓原子操作是指不会被线程调度机制打断的操作:这样的操作一旦開始,就一直运行到结束.中间不会 ...

  3. openldap+php-ldap操作

    一.基础知识首先,如果您对LDAP 不认识,建议先看看[原]LDAP服务介绍一文.本文以Linux 下常用的OpenLDAP为例说明.LDAP 以数方式存放数据,每个节点可存放属性或作为下面节点的父节 ...

  4. 根本上解决npm install 报错“ajv-keywords@3.4.0 requires a peer of ajv@^6.9.1 but none is installed. You must install peer dependencies yourself.“

    每次项目npm install 的时候都报这个错误, 然后网上找的方法就把这个 ajv重新安装下,感觉有点麻烦, 后来有次我把npm更新了一下(我的版本是: 6.1.0),更新到了最新版本,这个问题就 ...

  5. Node.js模块 require和 exports

    https://liuzhichao.com/p/1669.html http://www.cnblogs.com/pigtail/archive/2013/01/14/2859555.html

  6. linux机器之间配置ssh无密访问

    首先确认已安装了ssh服务,没装的自行百度一下. A机器:192.168.1.1 B机器:192.168.1.2 使A无密访问B,步骤如下[root@localhost ~]# cd .ssh 如果没 ...

  7. python 之 多线程

    一.多线程(具体可参照博文多进程---->http://www.cnblogs.com/work115/p/5621789.html) 1.函数式实现多线程 2.类实现多线程 3.多线程之线程锁 ...

  8. 开源的Eclipse的文件转码插件,可以在不影响中文的情况下改变项目文件编

    http://www.blogjava.net/lifesting/archive/2008/04/11/192250.html, 感谢此作者! 问题描述: 我们项目开发都统一采用utf-8格式编码, ...

  9. mysql insert中用case

    insert into urls(company,counterType,mdUrl,tradeUrl) values('test', CASE 'test'WHEN 'CTP' THEN 1WHEN ...

  10. CentOS安装Apache-2.4.25+安全配置

    注:以下所有操作均在CentOS 6.5 x86_64位系统下完成. #准备工作# 在安装Nginx之前,请确保已经使用yum安装了各基础组件,并且配置了www用户和用户组,具体见<CentOS ...