java序列化和反序列话总结
序列化:将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序列化和反序列话总结相关推荐
- java序列化的方法_【Java常见序列化与反序列方法总结】
人和电脑在很多方面都是十分相似的,大脑可以看成电脑主机,五官/身体等表面器官就是显示器.鼠标等外设.这篇文章就是想把计算机跟人做类比YY一下序列化和反序列化的机制.用途. 如果你是初学者,心里肯定会问 ...
- Java 序列化Serializable详解
转载 Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserializat ...
- java kiwi_【Java拾遗】不可不知的 Java 序列化
[Java拾遗]不可不知的 Java 序列化 前言 在程序运行的生命周期中,序列化与反序列化的操作,几乎无时无刻不在发生着.对于任何一门语言来说,不管它是编译型还是解释型,只要它需要通讯或者持久化时, ...
- 54. Java序列化三连问,是什么?为什么需要?如何实现?
Java序列化三连问,是什么?为什么需要?如何实现? 1,序列化和反序列化的概念 2,什么情况下需要序列化 3,java如何实现序列化 遇到这个 Java Serializable 序列化这个接口,我 ...
- Java序列化之serialVersionUID
Java序列化之serialVersionUID 今天讲一讲Java对象中的serialVersionUID,先从序列化讲起. 什么是序列化 序列化,简单的说,就是将一个对象转化(编码)成可以传输的输 ...
- Java序列化技术的知识与故事
Java序列化的学习 Java序列化的相关介绍 序列化的由来与意义 Java序列化概念 意义 举个栗子 结果展示 从上到下依次分析 Serializable标记接口 实现序列化 Serializabl ...
- 什么是java序列化_什么是Java序列化?为什么序列化?序列化有哪些方式?
先普及一下,计算机中无法识别一个基本单元[字节]来表示,必须经过"翻译"才能让计算机理解人类的语言,这个翻译过程就是[编码],通常所说的字符转换为字节. ?有I/O的地方机就会涉及 ...
- Java序列化和克隆
在学习编程的过程中,我觉得不止要获得课本的知识,更多的是通过学习技术知识提高解决问题的能力,这样我们才能走在最前方,本文主要讲述Java序列化和克隆,更多Java专业知识,广州疯狂java培训官网与你 ...
- 每日一博 - Java序列化一二事儿
文章目录 what Why 作用 常用API java.io.Serializable java.io.Externalizable java.io.ObjectOutputStream java.i ...
- Java 序列化Serializable详解(附详细例子)
Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization ...
最新文章
- 如何提高分布式系统的可用性
- JQuery元素选择器:和||,逻辑选择
- Machine Learning - Andrew Ng on Coursera (Week 6)
- IBM 计划在公司内部推行基于比特币的开源项目Hyperledger
- day28:检测磁盘io|自定义时段查看tomcat 日记|打印城市名字|代码上线|统计网站并发量...
- android dns解析
- bat窗口大小设置_8-Flink中的窗口
- C++描述杭电OJ 2005.第几天? ||
- 第二章16位和32位微处理器(1)——8086的编程结构与最小模式
- 什么是 Caché?
- windows froms 程序打包 (转载)
- 6to4隧道实验(华为设备)
- deepstream-test3
- 固态和机械硬盘组raid_相同大小的固态硬盘和机械硬盘组RAID1会不会影响固态硬盘的速度?...
- 笔记本电脑拔掉电源屏幕会黑一下怎么办
- 目标检测00-10:mmdetection(Foveabox为例)-源码无死角解析(3)-头部网络bbox_head-训练过程
- 利用ptython中的tutle画了一个表情包——2020冲冲冲!!
- 电脑开机后网络一直转圈,程序也打不开——亲测解决办法
- 天津大学智能车队大一培训前准备(2022)
- 推荐系统的评价指标总结