序列化:将java对象转换为字节序列的过程叫做序列化

反序列化:将字节对象转换为java对象的过程叫做反序列化

通常情况下,序列化有两种用途:、

1) 把对象的字节序列永久的保存在硬盘中

2)在网络上传输对象的字节序列

相应的API

  java.io.ObjectOutputStream

          writeObject(Object obj)

  java.io.ObjectInputStream

          readObject()

只有实现了Serializable或者Externalizable接口的类的对象才能够被序列化。否则当调用writeObject方法的时候会出现IOException。

需要注意的是Externalizable接口继承自Serializable接口。两者的区别如下:

  仅仅实现Serializable接口的类可应采用默认的序列化方式。比如String类。

    假设有一个Customer类的对象需要序列化,如果这个类仅仅实现了这个接口,那么序列化和反序列化的方式如下:ObjectOutputStream采用默认的序列化方式,对于这个类的非static,非transient的实例变量进行序列化。ObjectInputStream采用默认的反序列化方式,对于这个类的非static,非transient的实例变量进行反序列化。

    如果这个类不仅实现了Serializable接口,而且定义了readObject(ObjectInputStream in)和 writeObject(ObjectOutputStream out)方法,那么将按照如下的方式进行序列化和反序列化:ObjectOutputStream会调用这个类的writeObject方法进行序列化,ObjectInputStream会调用相应的readObject方法进行反序列化。

  实现Externalizable接口的类完全由自身来控制序列化的行为。而且必须实现writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。那么将按照如下的方式进行序列化和反序列化:ObjectOutputStream会调用这个类的writeExternal方法进行序列化,ObjectInputStream会调用相应的readExternal方法进行反序列化。

下面来看一个最简单的例子:

package com.java;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;public class simpleSerializableTest {public static void main(String[] args) throws Exception {ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("d:\\objectFile.obj"));String strObj="name";Customer customer=new Customer("rollen");//序列化,此处故意将同一对象序列化2次out.writeObject(strObj);out.writeObject(customer);out.writeObject(customer);out.close();//反序列化ObjectInputStream in=new ObjectInputStream(new FileInputStream("d:\\objectFile.obj"));String strobj1=(String)in.readObject();Customer cus1=(Customer)in.readObject();Customer cus2=(Customer)in.readObject();            in.close();System.out.println(strobj1+": "+cus1);System.out.println(strObj==strobj1);System.out.println(cus1==customer);System.out.println(cus1==cus2);}
}class Customer implements Serializable {private static final long serialVersionUID = 1L;private String name;public Customer() {System.out.println("无参构造方法");}public Customer(String name) {System.out.println("有参构造方法");this.name = name;}public String toString() {return "[ "+name+" ]";}}

输出结果为:

有参构造方法
name: [ rollen ]
false
false
true

可以看出,在进行反序列话的时候,并没有调用类的构造方法。而是直接根据他们的序列化数据在内存中创建新的对象。另外需要注意的是,如果由一个ObjectOutputStream对象多次序列化同一个对象,那么右一个objectInputStream对象反序列化后的也是同一个对象。(cus1==cus2结果为true可以看出)

看一段代码,证明static是不会被序列化的:

package com.java;import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;public class SerializableServer {public void send(Object obj) throws IOException {ServerSocket serverSocket = new ServerSocket(8000);while (true) {Socket socket = serverSocket.accept();ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());out.writeObject(obj);out.writeObject(obj);out.close();socket.close();}}public static void main(String[] args) throws Exception {Customer customer = new Customer("rollen", "male");new SerializableServer().send(customer);}
}class Customer implements Serializable {private static final long serialVersionUID = 1L;private String name;private static int count;private transient String sex;static {System.out.println("调用静态代码块");}public Customer() {System.out.println("无参构造方法");}public Customer(String name, String sex) {System.out.println("有参构造方法");this.name = name;this.sex = sex;count++;}public String toString() {return "[ " + count + " " + name + " " + sex + " ]";}
}

  

package com.java;import java.io.ObjectInputStream;
import java.net.Socket;public class SerializableClient {public void recive() throws Exception {Socket socket = new Socket("localhost", 8000);ObjectInputStream in = new ObjectInputStream(socket.getInputStream());Object obj1 = in.readObject();Object obj2 = in.readObject();System.out.println(obj1);System.out.println(obj1==obj2);}public static void main(String[] args) {try {new SerializableClient().recive();} catch (Exception e) {e.printStackTrace();}}
}

  运行结果中,count的值为0.

我们来看另外一种情况:

class A implements Serializable{B b;//...
}class B implements Serializable{//...
}

  当我们在序列化A的对象的时候,也会自动序列化和他相关联的B的对象。也就是说在默认的情况下,对象输出流会对整个对象图进行序列化。因此会导致出现下面的问题,看代码(例子中是使用双向列表作为内部结构的,只是给出了demo,并没有完整的实现,只是为了说明情况):

package com.java;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;public class SeriListTest implements Serializable {private static final long serialVersionUID = 1L;private int size;private Node head = null;private Node end = null;private static class Node implements Serializable {private static final long serialVersionUID = 1L;String data;Node next;Node previous;}// 列表末尾添加一个字符串public void add(String data) {Node node = new Node();node.data = data;node.next = null;node.previous = end;if (null != end) {end.next = node;}size++;end = node;if (size == 1) {head = end;}}public int getSize() {return size;}// other methods...public static void main(String[] args) throws Exception {SeriListTest list = new SeriListTest();for (int i = 0; i < 10000; ++i) {list.add("rollen" + i);}ByteArrayOutputStream buf = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(buf);out.writeObject(list);ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));list = (SeriListTest) in.readObject();System.out.println("size is :" + list.getSize());}}

  这段代码会出现如下错误:

Exception in thread "main" java.lang.StackOverflowError
  at java.lang.ref.ReferenceQueue.poll(ReferenceQueue.java:82)
  at java.io.ObjectStreamClass.processQueue(ObjectStreamClass.java:2234)
      ....

整个就是因为序列化的时候,对整个对象图进行序列化引起的问题。在这种情况下啊,我们需要自定义序列化的过程:

package com.java;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;public class SeriListTest implements Serializable {private static final long serialVersionUID = 1L;transient private int size;transient private Node head = null;transient private Node end = null;private static class Node implements Serializable {private static final long serialVersionUID = 1L;String data;Node next;Node previous;}// 列表末尾添加一个字符串public void add(String data) {Node node = new Node();node.data = data;node.next = null;node.previous = end;if (null != end) {end.next = node;}size++;end = node;if (size == 1) {head = end;}}public int getSize() {return size;}// other methods...private void writeObject(ObjectOutputStream outStream) throws IOException {outStream.defaultWriteObject();outStream.writeInt(size);for (Node node = head; node != null; node = node.next) {outStream.writeObject(node.data);}}private void readObject(ObjectInputStream inStream) throws IOException,ClassNotFoundException {inStream.defaultReadObject();int count = inStream.readInt();for (int i = 0; i < count; ++i) {add((String) inStream.readObject());}}public static void main(String[] args) throws Exception {SeriListTest list = new SeriListTest();for (int i = 0; i < 10000; ++i) {list.add("rollen" + i);}ByteArrayOutputStream buf = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(buf);out.writeObject(list);ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));list = (SeriListTest) in.readObject();System.out.println("size is :" + list.getSize());}}

  运行结果为:10000

现在我们总结一下,在什么情况下我们需要自定义序列化的方式:

  1)为了确保序列化的安全性,对于一些敏感信息加密

  2)确保对象的成员变量符合正确的约束条件

  3)优化序列化的性能(之前的那个例子已经解释了这种情况)

下面我们来用例子解释一下这些:

先来看看:为了确保序列化的安全性,对于一些敏感信息加密

package com.java;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;public class SeriDemo1 implements Serializable {private String name;transient private String password; // 注意此处的transientpublic SeriDemo1() {}public SeriDemo1(String name, String password) {this.name = name;this.password = password;}// 此处模拟对密码进行加密,进行了简化private String change(String password) {return password + "rollen";}private void writeObject(ObjectOutputStream outStream) throws IOException {outStream.defaultWriteObject();outStream.writeObject(change(password));}private void readObject(ObjectInputStream inStream) throws IOException,ClassNotFoundException {inStream.defaultReadObject();String strPassowrd = (String) inStream.readObject();//此处模拟对密码解密password = strPassowrd.substring(0, strPassowrd.length() - 6);}@Overridepublic String toString() {return "SeriDemo1 [name=" + name + ", password=" + password + "]";}public static void main(String[] args) throws Exception {SeriDemo1 demo = new SeriDemo1("hello", "1234");ByteArrayOutputStream buf = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(buf);out.writeObject(demo);ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));demo = (SeriDemo1) in.readObject();System.out.println(demo);}
}

  然后我们看看:确保对象的成员变量符合正确的约束条件。比如一般情况下我们会在构造函数中对于参数进行合法性检查,但是默认的序列化并不会调用类的构造函数,直接由对象的序列化数据来构造出一个对象,这个我们就有可能提供遗传非法的序列化数据,来构造一个不满足约束条件的对象。

为了避免这种情况,我们可以自定义反序列话的方式。比如在readObject方法中,进行检查。当数据不满足约束的时候(比如年龄小于0等等不满足约束的情况),可以抛出异常之类的。

接下来我们看看readResolve()方法在单例模式中的使用:

单例模式大家应该都清楚,我就不多说了,看看下面的代码:

package com.java;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;public class ReadResolveDemo implements Serializable {private static final long serialVersionUID = 1L;private ReadResolveDemo() {}public static ReadResolveDemo getInstance() {return new ReadResolveDemo();}public static void main(String[] args) throws Exception {ReadResolveDemo demo=ReadResolveDemo.getInstance();ByteArrayOutputStream buf = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(buf);out.writeObject(demo);ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();System.out.println(demo==demo1); //false}
}

  本来单例模式中,只有一个实例,但是在序列化的时候,无论采用默认的方式,还是自定义的方式,在反序列化的时候都会产生一个新的对象,所以上面的程序运行输出false。

因此可以看出反序列化打破了单例模式只有一个实例的约定,为了避免这种情况,我们可以使用readReslove

package com.java;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;public class ReadResolveDemo implements Serializable {private static final long serialVersionUID = 1L;private static final ReadResolveDemo INSTANCE = new ReadResolveDemo();private ReadResolveDemo() {}public static ReadResolveDemo getInstance() {return INSTANCE;}private Object readResolve() {return INSTANCE;}public static void main(String[] args) throws Exception {ReadResolveDemo demo = ReadResolveDemo.getInstance();ByteArrayOutputStream buf = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(buf);out.writeObject(demo);ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();System.out.println(demo == demo1); // true}
}

  最后我们简单的说一下实现Externalizable接口。 实现Externalizable接口的类完全由自身来控制序列化的行为。而且必须实现writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。

注意在对实现了这个接口的对象进行反序列化的时候,会先调用类的不带参数的构造函数,这个和之前的默认反序列化方式是不一样的。

例子如下:

package com.java;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;public class ExternalizableDemo implements Externalizable {private String name;static {System.out.println("调用静态代码块");}public ExternalizableDemo() {System.out.println("调用默认无参构造函数");}public ExternalizableDemo(String name) {this.name = name;System.out.println("调用有参构造函数");}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(name);}@Overridepublic void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {name = (String) in.readObject();}@Overridepublic String toString() {return "[" + name + "]";}public static void main(String[] args) throws Exception {ExternalizableDemo demo = new ExternalizableDemo("rollen");ByteArrayOutputStream buf = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(buf);out.writeObject(demo);ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));demo = (ExternalizableDemo) in.readObject();System.out.println(demo);}
}

  输出:

调用静态代码块
调用有参构造函数
调用默认无参构造函数
[rollen]

 参考资料:

1.java序列化高级认识

java序列化和反序列话总结相关推荐

  1. java序列化的方法_【Java常见序列化与反序列方法总结】

    人和电脑在很多方面都是十分相似的,大脑可以看成电脑主机,五官/身体等表面器官就是显示器.鼠标等外设.这篇文章就是想把计算机跟人做类比YY一下序列化和反序列化的机制.用途. 如果你是初学者,心里肯定会问 ...

  2. Java 序列化Serializable详解

    转载 Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserializat ...

  3. java kiwi_【Java拾遗】不可不知的 Java 序列化

    [Java拾遗]不可不知的 Java 序列化 前言 在程序运行的生命周期中,序列化与反序列化的操作,几乎无时无刻不在发生着.对于任何一门语言来说,不管它是编译型还是解释型,只要它需要通讯或者持久化时, ...

  4. 54. Java序列化三连问,是什么?为什么需要?如何实现?

    Java序列化三连问,是什么?为什么需要?如何实现? 1,序列化和反序列化的概念 2,什么情况下需要序列化 3,java如何实现序列化 遇到这个 Java Serializable 序列化这个接口,我 ...

  5. Java序列化之serialVersionUID

    Java序列化之serialVersionUID 今天讲一讲Java对象中的serialVersionUID,先从序列化讲起. 什么是序列化 序列化,简单的说,就是将一个对象转化(编码)成可以传输的输 ...

  6. Java序列化技术的知识与故事

    Java序列化的学习 Java序列化的相关介绍 序列化的由来与意义 Java序列化概念 意义 举个栗子 结果展示 从上到下依次分析 Serializable标记接口 实现序列化 Serializabl ...

  7. 什么是java序列化_什么是Java序列化?为什么序列化?序列化有哪些方式?

    先普及一下,计算机中无法识别一个基本单元[字节]来表示,必须经过"翻译"才能让计算机理解人类的语言,这个翻译过程就是[编码],通常所说的字符转换为字节. ?有I/O的地方机就会涉及 ...

  8. Java序列化和克隆

    在学习编程的过程中,我觉得不止要获得课本的知识,更多的是通过学习技术知识提高解决问题的能力,这样我们才能走在最前方,本文主要讲述Java序列化和克隆,更多Java专业知识,广州疯狂java培训官网与你 ...

  9. 每日一博 - Java序列化一二事儿

    文章目录 what Why 作用 常用API java.io.Serializable java.io.Externalizable java.io.ObjectOutputStream java.i ...

  10. Java 序列化Serializable详解(附详细例子)

    Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization ...

最新文章

  1. 如何提高分布式系统的可用性
  2. JQuery元素选择器:和||,逻辑选择
  3. Machine Learning - Andrew Ng on Coursera (Week 6)
  4. IBM 计划在公司内部推行基于比特币的开源项目Hyperledger
  5. day28:检测磁盘io|自定义时段查看tomcat 日记|打印城市名字|代码上线|统计网站并发量...
  6. android dns解析
  7. bat窗口大小设置_8-Flink中的窗口
  8. C++描述杭电OJ 2005.第几天? ||
  9. 第二章16位和32位微处理器(1)——8086的编程结构与最小模式
  10. 什么是 Caché?
  11. windows froms 程序打包 (转载)
  12. 6to4隧道实验(华为设备)
  13. deepstream-test3
  14. 固态和机械硬盘组raid_相同大小的固态硬盘和机械硬盘组RAID1会不会影响固态硬盘的速度?...
  15. 笔记本电脑拔掉电源屏幕会黑一下怎么办
  16. 目标检测00-10:mmdetection(Foveabox为例)-源码无死角解析(3)-头部网络bbox_head-训练过程
  17. 利用ptython中的tutle画了一个表情包——2020冲冲冲!!
  18. 电脑开机后网络一直转圈,程序也打不开——亲测解决办法
  19. 天津大学智能车队大一培训前准备(2022)
  20. 推荐系统的评价指标总结

热门文章

  1. [导入]如何调试你的C#程序
  2. IEEE_Tec_Digtal Signal Analog Signal
  3. 读《单页web应用》-回顾作用域
  4. SP2010开发和VS2010专家食谱--第七章节--使用客户端对象模型
  5. !!终于把【库存管理的大致功能完成了!】
  6. jQuery性能优化指南(2)
  7. SAS的SQL过程(SQL Procedure in SAS)(一)
  8. GCC O1 O2 O3
  9. js调用局部打印功能并还原
  10. python学习第十八天 --文件操作