Java对象表示方式1:序列化、反序列化的作用
1.序列化是的作用和用途
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。
2.序列化的步骤
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。
对象序列化包括如下步骤:
1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
2) 通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤如下:
1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
2) 通过对象输入流的readObject()方法读取对象。
3.默认的序列化
序列化只需要实现java.io.Serializable接口就可以了。序列化的时候有一个serialVersionUID参数,Java序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化,Java虚拟机会把传过来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的实体类,可以进行反序列化,否则Java虚拟机会拒绝对这个实体类进行反序列化并抛出异常。serialVersionUID有两种生成方式:
1、默认的1L
2、根据类名、接口名、成员方法以及属性等来生成一个64位的Hash字段
如果实现java.io.Serializable接口的实体类没有显式定义一个名为serialVersionUID、类型为long的变量时,Java序列化机制会根据编译的.class文件自动生成一个serialVersionUID,如果.class文件没有变化,那么就算编译再多次,serialVersionUID也不会变化。换言之,Java为用户定义了默认的序列化、反序列化方法,其实就是ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法。看一个例子:
![](https://common.cnblogs.com/images/copycode.gif)
1 public class SerializableObject implements Serializable
2 {
3 private static final long serialVersionUID = 1L;
4
5 private String str0;
6 private transient String str1;
7 private static String str2 = "abc";
8
9 public SerializableObject(String str0, String str1)
10 {
11 this.str0 = str0;
12 this.str1 = str1;
13 }
14
15 public String getStr0()
16 {
17 return str0;
18 }
19
20 public String getStr1()
21 {
22 return str1;
23 }
24 }
![](https://common.cnblogs.com/images/copycode.gif)
![](https://common.cnblogs.com/images/copycode.gif)
1 public static void main(String[] args) throws Exception
2 {
3 File file = new File("D:" + File.separator + "s.txt");
4 OutputStream os = new FileOutputStream(file);
5 ObjectOutputStream oos = new ObjectOutputStream(os);
6 oos.writeObject(new SerializableObject("str0", "str1"));
7 oos.close();
8
9 InputStream is = new FileInputStream(file);
10 ObjectInputStream ois = new ObjectInputStream(is);
11 SerializableObject so = (SerializableObject)ois.readObject();
12 System.out.println("str0 = " + so.getStr0());
13 System.out.println("str1 = " + so.getStr1());
14 ois.close();
15 }
![](https://common.cnblogs.com/images/copycode.gif)
先不运行,用一个二进制查看器查看一下s.txt这个文件,并详细解释一下每一部分的内容。
第1部分是序列化文件头
◇AC ED:STREAM_MAGIC序列化协议
◇00 05:STREAM_VERSION序列化协议版本
◇73:TC_OBJECT声明这是一个新的对象
第2部分是要序列化的类的描述,在这里是SerializableObject类
◇72:TC_CLASSDESC声明这里开始一个新的class
◇00 1F:十进制的31,表示class名字的长度是31个字节
◇63 6F 6D ... 65 63 74:表示的是“com.xrq.test.SerializableObject”这一串字符,可以数一下确实是31个字节
◇00 00 00 00 00 00 00 01:SerialVersion,序列化ID,1
◇02:标记号,声明该对象支持序列化
◇00 01:该类所包含的域的个数为1个
第3部分是对象中各个属性项的描述
◇4C:字符"L",表示该属性是一个对象类型而不是一个基本类型
◇00 04:十进制的4,表示属性名的长度
◇73 74 72 30:字符串“str0”,属性名
◇74:TC_STRING,代表一个new String,用String来引用对象
第4部分是该对象父类的信息,如果没有父类就没有这部分。有父类和第2部分差不多
◇00 12:十进制的18,表示父类的长度
◇4C 6A 61 ... 6E 67 3B:“L/java/lang/String;”表示的是父类属性
◇78:TC_ENDBLOCKDATA,对象块结束的标志
◇70:TC_NULL,说明没有其他超类的标志
第5部分输出对象的属性项的实际值,如果属性项是一个对象,这里还将序列化这个对象,规则和第2部分一样
◇00 04:十进制的4,属性的长度
◇73 74 72 30:字符串“str0”,str0的属性值
从以上对于序列化后的二进制文件的解析,我们可以得出以下几个关键的结论:
1、序列化之后保存的是类的信息
2、被声明为transient的属性不会被序列化,这就是transient关键字的作用
3、被声明为static的属性不会被序列化,这个问题可以这么理解,序列化保存的是对象的状态,但是static修饰的变量是属于类的而不是属于变量的,因此序列化的时候不会序列化它
接下来运行一下上面的代码看一下
str0 = str0
str1 = null
因为str1是一个transient类型的变量,没有被序列化,因此反序列化出来也是没有任何内容的,显示的null,符合我们的结论。
4.defaultWriteObject和defaultReadObject(手动指定序列化)
4.1手动指定序列化
Java并不强求用户非要使用默认的序列化方式,用户也可以按照自己的喜好自己指定自己想要的序列化方式----只要你自己能保证序列化前后能得到想要的数据就好了。手动指定序列化方式的规则是:
进行序列化、反序列化时,虚拟机会首先试图调用对象里的writeObject和readObject方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,那么默认调用的是ObjectOutputStream的defaultWriteObject以及ObjectInputStream的defaultReadObject方法。换言之,利用自定义的writeObject方法和readObject方法,用户可以自己控制序列化和反序列化的过程。
这是非常有用的。比如:
1、有些场景下,某些字段我们并不想要使用Java提供给我们的序列化方式,而是想要以自定义的方式去序列化它,比如ArrayList的elementData、HashMap的table(至于为什么在之后写这两个类的时候会解释原因),就可以通过将这些字段声明为transient,然后在writeObject和readObject中去使用自己想要的方式去序列化它们
2、因为序列化并不安全,因此有些场景下我们需要对一些敏感字段进行加密再序列化,然后再反序列化的时候按照同样的方式进行解密,就在一定程度上保证了安全性了。要这么做,就必须自己写writeObject和readObject,writeObject方法在序列化前对字段加密,readObject方法在序列化之后对字段解密
上面的例子SerializObject这个类修改一下,主函数不需要修改:
![](https://common.cnblogs.com/images/copycode.gif)
1 public class SerializableObject implements Serializable
2 {
3 private static final long serialVersionUID = 1L;
4
5 private String str0;
6 private transient String str1;
7 private static String str2 = "abc";
8
9 public SerializableObject(String str0, String str1)
10 {
11 this.str0 = str0;
12 this.str1 = str1;
13 }
14
15 public String getStr0()
16 {
17 return str0;
18 }
19
20 public String getStr1()
21 {
22 return str1;
23 }
24
25 private void writeObject(java.io.ObjectOutputStream s) throws Exception
26 {
27 System.out.println("我想自己控制序列化的过程");
28 s.defaultWriteObject();
29 s.writeInt(str1.length());
30 for (int i = 0; i < str1.length(); i++)
31 s.writeChar(str1.charAt(i));
32 }
33
34 private void readObject(java.io.ObjectInputStream s) throws Exception
35 {
36 System.out.println("我想自己控制反序列化的过程");
37 s.defaultReadObject();
38 int length = s.readInt();
39 char[] cs = new char[length];
40 for (int i = 0; i < length; i++)
41 cs[i] = s.readChar();
42 str1 = new String(cs, 0, length);
43 }
44 }
![](https://common.cnblogs.com/images/copycode.gif)
直接看一下运行结果:
我想自己控制序列化的过程
我想自己控制反序列化的过程
str0 = str0
str1 = str1
看到,程序走到了我们自己写的writeObject和readObject中,而且被transient修饰的str1也成功序列化、反序列化出来了----因为手动将str1写入了文件和从文件中读了出来。不妨再看一下s.txt文件的二进制:
看到橘黄色的部分就是writeObject方法追加的str1的内容。至此,总结一下writeObject和readObject的通常用法:
先通过defaultWriteObject和defaultReadObject方法序列化、反序列化对象,然后在文件结尾追加需要额外序列化的内容/从文件的结尾读取额外需要读取的内容。
4.2 通过这种方式达到 序列化static和transient变量的目的
1 /**
2 * 序列化的演示测试程序
3 *
4 * @author skywang
5 */
6
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.io.Serializable;
12 import java.io.IOException;
13 import java.lang.ClassNotFoundException;
14
15 public class SerialTest5 {
16 private static final String TMP_FILE = ".serialtest5.txt";
17
18 public static void main(String[] args) {
19 // 将“对象”通过序列化保存
20 testWrite();
21 // 将序列化的“对象”读出来
22 testRead();
23 }
24
25
26 /**
27 * 将Box对象通过序列化,保存到文件中
28 */
29 private static void testWrite() {
30 try {
31 // 获取文件TMP_FILE对应的对象输出流。
32 // ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
33 ObjectOutputStream out = new ObjectOutputStream(
34 new FileOutputStream(TMP_FILE));
35 // 创建Box对象,Box实现了Serializable序列化接口
36 Box box = new Box("desk", 80, 48);
37 // 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
38 out.writeObject(box);
39 // 打印“Box对象”
40 System.out.println("testWrite box: " + box);
41 // 修改box的值
42 box = new Box("room", 100, 50);
43
44 out.close();
45 } catch (Exception ex) {
46 ex.printStackTrace();
47 }
48 }
49
50 /**
51 * 从文件中读取出“序列化的Box对象”
52 */
53 private static void testRead() {
54 try {
55 // 获取文件TMP_FILE对应的对象输入流。
56 ObjectInputStream in = new ObjectInputStream(
57 new FileInputStream(TMP_FILE));
58 // 从对象输入流中,读取先前保存的box对象。
59 Box box = (Box) in.readObject();
60 // 打印“Box对象”
61 System.out.println("testRead box: " + box);
62 in.close();
63 } catch (Exception e) {
64 e.printStackTrace();
65 }
66 }
67 }
68
69
70 /**
71 * Box类“支持序列化”。因为Box实现了Serializable接口。
72 *
73 * 实际上,一个类只需要实现Serializable即可实现序列化,而不需要实现任何函数。
74 */
75 class Box implements Serializable {
76 private static int width;
77 private transient int height;
78 private String name;
79
80 public Box(String name, int width, int height) {
81 this.name = name;
82 this.width = width;
83 this.height = height;
84 }
85
86 private void writeObject(ObjectOutputStream out) throws IOException{
87 out.defaultWriteObject();//使定制的writeObject()方法可以利用自动序列化中内置的逻辑。
88 out.writeInt(height);
89 out.writeInt(width);
90 //System.out.println("Box--writeObject width="+width+", height="+height);
91 }
92
93 private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{
94 in.defaultReadObject();//defaultReadObject()补充自动序列化
95 height = in.readInt();
96 width = in.readInt();
97 //System.out.println("Box---readObject width="+width+", height="+height);
98 }
99
100 @Override
101 public String toString() {
102 return "["+name+": ("+width+", "+height+") ]";
103 }
104 }
运行结果:
testWrite box: [desk: (80, 48) ]
testRead box: [desk: (80, 48) ]
程序说明:
“序列化不会自动保存static和transient变量”,因此我们若要保存它们,则需要通过writeObject()和readObject()去手动读写。
(01) 通过writeObject()方法,写入要保存的变量。writeObject的原始定义是在ObjectOutputStream.java中,我们按照如下示例覆盖即可:
private void writeObject(ObjectOutputStream out) throws IOException{
out.defaultWriteObject();// 使定制的writeObject()方法可以利用自动序列化中内置的逻辑。
out.writeInt(ival); // 若要保存“int类型的值”,则使用writeInt()
out.writeObject(obj); // 若要保存“Object对象”,则使用writeObject()
}
(02) 通过readObject()方法,读取之前保存的变量。readObject的原始定义是在ObjectInputStream.java中,我们按照如下示例覆盖即可:
private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{
in.defaultReadObject(); // 使定制的readObject()方法可以利用自动序列化中内置的逻辑。
int ival = in.readInt(); // 若要读取“int类型的值”,则使用readInt()
Object obj = in.readObject(); // 若要读取“Object对象”,则使用readObject()
}
至此,我们就介绍完了“序列化对static和transient变量的处理”。
5.Externalizable
如果一个类要完全负责自己的序列化,则实现Externalizable接口,而不是Serializable接口。
Externalizable:他是Serializable接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性。
需要注意的是:声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。
/**
* 序列化的演示测试程序
*
* @author skywang
*/ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectOutput;
import java.io.ObjectInput;
import java.io.Serializable;
import java.io.Externalizable;
import java.io.IOException;
import java.lang.ClassNotFoundException; public class ExternalizableTest2 {
private static final String TMP_FILE = ".externalizabletest2.txt"; public static void main(String[] args) {
// 将“对象”通过序列化保存
testWrite();
// 将序列化的“对象”读出来
testRead();
} /**
* 将Box对象通过序列化,保存到文件中
*/
private static void testWrite() {
try {
// 获取文件TMP_FILE对应的对象输出流。
// ObjectOutputStream中,只能写入“基本数据”或“支持序列化的对象”
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream(TMP_FILE));
// 创建Box对象
Box box = new Box("desk", 80, 48);
// 将box对象写入到对象输出流out中,即相当于将对象保存到文件TMP_FILE中
out.writeObject(box);
// 打印“Box对象”
System.out.println("testWrite box: " + box); out.close();
} catch (Exception ex) {
ex.printStackTrace();
}
} /**
* 从文件中读取出“序列化的Box对象”
*/
private static void testRead() {
try {
// 获取文件TMP_FILE对应的对象输入流。
ObjectInputStream in = new ObjectInputStream(
new FileInputStream(TMP_FILE));
// 从对象输入流中,读取先前保存的box对象。
Box box = (Box) in.readObject();
// 打印“Box对象”
System.out.println("testRead box: " + box);
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* Box类实现Externalizable接口
*/
class Box implements Externalizable {
private int width;
private int height;
private String name; public Box() {
} public Box(String name, int width, int height) {
this.name = name;
this.width = width;
this.height = height;
} @Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(width);
out.writeInt(height);
} @Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
width = in.readInt();
height = in.readInt();
} @Override
public String toString() {
return "["+name+": ("+width+", "+height+") ]";
}
}
运行结果:
testWrite box: [desk: (80, 48) ]
testRead box: [null: (0, 0) ]
注意事项:
(01) 实现Externalizable接口的类,不会像实现Serializable接口那样,会自动将数据保存。
(02) 实现Externalizable接口的类,必须实现writeExternal()和readExternal()接口!否则,程序无法正常编译!
(03) 实现Externalizable接口的类,必须定义不带参数的构造函数!会默认的调用构造函数,否则,程序无法正常编译!
(04) writeExternal() 和 readExternal() 的方法都是public的,不是非常安全!
6.复杂序列化情况总结
虽然Java的序列化能够保证对象状态的持久保存,但是遇到一些对象结构复杂的情况还是比较难处理的,最后对一些复杂的对象情况作一个总结:
1、当父类继承Serializable接口时,所有子类都可以被序列化
2、子类实现了Serializable接口,父类没有,父类中的属性不能序列化(不报错,数据丢失),但是在子类中属性仍能正确序列化
3、如果序列化的属性是对象,则这个对象也必须实现Serializable接口,否则会报错
4、反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错
5、反序列化时,如果serialVersionUID被修改,则反序列化时会失败
转载:http://www.cnblogs.com/xrq730/p/4821958.html
http://www.cnblogs.com/skywang12345/p/io_06.html
Java对象表示方式1:序列化、反序列化的作用的更多相关文章
- Java对象表示方式1:序列化、反序列化和transient关键字的作用
平时我们在Java内存中的对象,是无法进行IO操作或者网络通信的,因为在进行IO操作或者网络通信的时候,人家根本不知道内存中的对象是个什么东西,因此必须将对象以某种方式表示出来,即存储对象中的状态.一 ...
- Java对象表示方式2:XStream实现对对象的XML化
上一篇文章讲到了使用Java原生的序列化的方式来表示一个对象.总结一下这种对象表示方式的优缺点: 1.纯粹的Java环境下这种方式可以很好地工作,因为它是Java自带的,也不需要第三方的Jar包的支持 ...
- java对象表示方式--XStream
对象表示有各种各样的方式,序列化只是其中的一种而已.表示一个对象的目的无非就是为了对象<---->IO之间相互认识,至于怎么认识,那就有很多选择了.除了之前讲过的序列化,还可以选择将数据J ...
- json工具性能比较:json-lib和jackson进行Java对象到json字符串序列化[转]
网上查找“java json”,发现大家使用最多的还是json-lib来进行java对象的序列化成json对象和反序列化成java对象的操作.但是之前在网上也看到过一往篇关于json序列化性能比较的文 ...
- JAVA对象实例化方式总结
JAVA对象实例化的方法 New对象实例 // 直接new对象实例 Productor productor = new Productor(); 反射机制 Java反射机制是在运行状态中,对于任意一个 ...
- 线程实现方式以及序列化 反序列化.java
一.序列化与反序列化 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上, ...
- Java 对象拷贝方式
(1)BeanUtils.cloneBean()使用: http://www.cnblogs.com/fervour/archive/2009/12/18/1627868.html package c ...
- 一文带你全面了解java对象的序列化和反序列化
摘要:这篇文章主要给大家介绍了关于java中对象的序列化与反序列化的相关内容,文中通过详细示例代码介绍,希望能对大家有所帮助. 本文分享自华为云社区<java中什么是序列化和反序列化?>, ...
- Java 对象的序列化、反序列化
对象的序列化(Serialize):将内存中的Java对象转换为与平台无关的二进制流(字节序列),然后存储在磁盘文件中,或通过网络传输给另一个网络节点. 对象的反序列化(Deserialize):获取 ...
随机推荐
- 九度oj题目1019:简单计算器
题目1019:简单计算器 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:6346 解决:2334 题目描述: 读入一个只包含 +, -, *, / 的非负整数计算表达式,计算该表达 ...
- Java入门系列-18-抽象类和接口
抽象类 在第16节继承中,有父类 People People people=new People(); people.sayHi(); 实例化People是没有意义的,因为"人"是 ...
- [转]How to use an Area in ASP.NET Core
本文转自:http://stackoverflow.com/questions/36535511/how-to-use-an-area-in-asp-net-core Q: How does one ...
- springmvc4集成swagger2
首先在原有的springmvc工程的pom文件中增加swagger <dependency> <groupId>io.springfox</groupId> < ...
- 【3】.net MVC 使用IPrincipal进行Form登录即权限验证
1.在MVC项目中添加用户类,可以根据实际项目需求添加必要属性 public class UserData { /// <summary> /// ID /// </summary& ...
- 七、集成swagger2
1.添加依赖 <!-- swager2 --> <dependency> <groupId>io.springfox</groupId> <art ...
- Linux From Scratch(从零开始构建Linux系统,简称LFS)(一)
一. 准备工作 1. 需要一个Linux宿主系统,例如早先版本的 LFS,Ubuntu/Fedora,SuSE 或者是在你的架构上可以运行的其它发行版 如果想实现Win7与Linux双系统,可参考我的 ...
- thinkphp下判断状态值语法
在thinkphp框架下我们经常会用到状态值的判断:但是这样写会引起语法错误. <div> <if condition="{$res.status} eq '0'" ...
- 第3章 css属性color的RGBA值
颜色之RGBA RGB是一种色彩标准,是由红(R).绿(G).蓝(B)的变化以及相互叠加来得到各式各样的颜色.RGBA是在RGB的基础上增加了控制alpha透明度的参数. 语法: color:rgba ...
- 表单校验常用原生js库
1.字符串去除左右空格继承形式// 除去左右空格String.prototype.Trim = function() { return this.replace(/(^\s*)|(\s*$)/g, & ...