对象的序列化和反序列化浅析
- 什么是序列化和反序列化?
对象序列化,就是将Object转换成byte序列,反之叫对象的反序列化
序列化流(ObjectOutputStream),是过滤流----writeObject
反序列化流(ObjectInputStream)---readObject
- 序列化接口(Serializable)介绍
这是一个位于java.io包下的空接口,没有任何方法,只是一个标准,这不更是接口的精髓所在嘛,哈哈,对于接口的概念还比较模糊的朋友可参考我的这一篇博文:Java基础之抽象类与接口
此外,对象必须实现此序列化接口 ,才能进行序列化,否则将抛出异常
- transient关键字
在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
总之,java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
对于加了transient关键字的属性但自己需要自己进行序列化时,我们可以定义这两个方法(参考ArrayList源码)
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException
- 代码分析:
定义一个学生类

1 package me.io.seri;
2
3 import java.io.Serializable;
4
5 /**
6 * 学生类
7 *
8 * @author Administrator
9 *
10 */
11 public class Student implements Serializable {
12
13 private static final long serialVersionUID = 1L;
14 private String stuNum;
15 private String stuName;
16 // 该元素不会被jvm进行默认的序列化,也可以自己进行序列化
17 private transient int stuAge;
18
19 public Student() {
20 }
21
22 public Student(String stuNum, String stuName, int stuAge) {
23 super();
24 this.stuNum = stuNum;
25 this.stuName = stuName;
26 this.stuAge = stuAge;
27 }
28
29 public String getStuName() {
30 return stuName;
31 }
32
33 public void setStuName(String stuName) {
34 this.stuName = stuName;
35 }
36
37 public int getStuAge() {
38 return stuAge;
39 }
40
41 public void setStuAge(int stuAge) {
42 this.stuAge = stuAge;
43 }
44
45 public String getStuNum() {
46 return stuNum;
47 }
48
49 public void setStuNum(String stuNum) {
50 this.stuNum = stuNum;
51 }
52
53 @Override
54 public String toString() {
55 return "Student [stuNum=" + stuNum + ", stuName=" + stuName + ", stuAge=" + stuAge + "]";
56 }
57
58 private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
59
60 s.defaultWriteObject();// 把JVM能默认序列化的元素进行序列化操作
61 s.writeInt(stuAge);// 自己完成stuAge的序列化操作
62 }
63
64 private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
65 s.defaultReadObject();// 把JVM默认反序列化的元素进行反序列化操作
66 this.stuAge = s.readInt();// 自己完成stuAge的反序列化操作
67 }
68 }
对象序列化和反序列化示例:

1 package me.io.seri;
2
3 import java.io.FileInputStream;
4 import java.io.FileOutputStream;
5 import java.io.IOException;
6 import java.io.ObjectInputStream;
7 import java.io.ObjectOutputStream;
8
9 /**
10 * 对象序列化和反序列化示例
11 *
12 * @author Administrator
13 *
14 */
15 public class ObjectSeriaUtil {
16
17 public static ObjectOutputStream oos = null;
18 public static ObjectInputStream ois = null;
19 public static String file = null;
20
21 public static void setFile(String file) {
22 ObjectSeriaUtil.file = file;
23 }
24
25 /**
26 * 对象的序列化
27 *
28 * @param obj
29 * @throws IOException
30 */
31 public static void ObjectSerialize(Object obj) throws IOException {
32 oos = new ObjectOutputStream(new FileOutputStream(file));
33 oos.writeObject(obj);
34 oos.flush();
35 oos.close();
36 }
37
38 /**
39 * 对象反序列化
40 * @throws IOException
41 * @throws ClassNotFoundException
42 */
43 public static void ObjectDeserialize() throws IOException, ClassNotFoundException {
44 ois = new ObjectInputStream(new FileInputStream(file));
45 System.out.println(ois.readObject());
46 ois.close();
47 }
48
49 }
main方法进行测试:

1 package me.io.seri;
2
3 import java.io.IOException;
4
5 public class SeriaMain {
6
7 public static void main(String[] args) throws IOException, ClassNotFoundException {
8 ObjectSeriaUtil.setFile("demo/obj.dat");
9 ObjectSeriaUtil.ObjectSerialize(new Student("10003", "Rose", 18));
10 ObjectSeriaUtil.ObjectDeserialize();
11 }
12 }
- 分析ArrayList源码中序列化和反序列化的问题
我们都知道ArrayList是基于动态数组实现的一个操作集合的工具类,它实现了Serializable接口,意味着ArrayList可以进行序列化,关于ArrayList我在前面的博文中也有详细的介绍,不太清楚的朋友们可以看看我的这篇博文:
先来看看ArrayList中这个动态数组的定义:
transient Object[] elementData;
就这么简短的一句,主要是前面的transient关键字,这意味着elementData不会被JVM默认的进行序列化,ArrayList会对它进行序列化
我们来看看ArrayList的两个方法:
这个方法中ArrayList使用for循环对elementData进行了序列化
1 private void readObject(java.io.ObjectInputStream s)
2 throws java.io.IOException, ClassNotFoundException {
3 elementData = EMPTY_ELEMENTDATA;
4
5 // Read in size, and any hidden stuff
6 s.defaultReadObject();
7
8 // Read in capacity
9 s.readInt(); // ignored
10
11 if (size > 0) {
12 // be like clone(), allocate array based upon size not capacity
13 ensureCapacityInternal(size);
14
15 Object[] a = elementData;
16 // Read in all elements in the proper order.
17 for (int i=0; i<size; i++) {
18 a[i] = s.readObject();
19 }
20 }
21 }
同样,这个方法中ArrayList使用for循环对elementData进行了反序列化操作
1 private void writeObject(java.io.ObjectOutputStream s)
2 throws java.io.IOException{
3 // Write out element count, and any hidden stuff
4 int expectedModCount = modCount;
5 s.defaultWriteObject();
6
7 // Write out size as capacity for behavioural compatibility with clone()
8 s.writeInt(size);
9
10 // Write out all elements in the proper order.
11 for (int i=0; i<size; i++) {
12 s.writeObject(elementData[i]);
13 }
14
15 if (modCount != expectedModCount) {
16 throw new ConcurrentModificationException();
17 }
18 }
为什么要这么做呢?
来看看ArrayList中成员变量size的定义:
1 /**
2 * The size of the ArrayList (the number of elements it contains).
3 *
4 * @serial
5 */
6 private int size;
可见,size表示ArrayList中当前包含的元素的个数
再回头看看readObject和writeObject这两个方法中的for循环,相信大家应该已经理解了吧
Java类库的设计者是为了提高ArrayList的性能才这样去做的,显然由于ArrayList的动态扩容机制,ArrayList中包含的元素的个数总是小于ArrayList的容量,因此对ArrayList做序列化操作时,对于动态数组根据其元素的个数做序列化显然比直接对整个数组序列化性能会高一些。
- 序列化中 子类和父类构造函数的调用问题
代码分析:

1 package me.io.seri;
2
3 import java.io.FileInputStream;
4 import java.io.FileOutputStream;
5 import java.io.ObjectInputStream;
6 import java.io.ObjectOutputStream;
7 import java.io.Serializable;
8
9 public class SerializableDemo {
10 public static void main(String[] args) throws Exception {
11 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("demo/obj1.dat"));
12 Foo2 foo2 = new Foo2();
13 oos.writeObject(foo2);
14 oos.flush();
15 oos.close();
16
17 // 反序列化是否递归调用父类的构造函数
18 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("demo/obj1.dat"));
19 Foo2 foo21 = (Foo2) ois.readObject();
20 System.out.println(foo21);
21 ois.close();
22
23 ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("demo/obj1.dat"));
24 Bar2 bar2 = new Bar2();
25 oos1.writeObject(bar2);
26 oos1.flush();
27 oos1.close();
28
29 ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream("demo/obj1.dat"));
30 Bar2 bar21 = (Bar2) ois1.readObject();
31 System.out.println(bar21);
32 ois1.close();
33
34 /*
35 * 对子类对象进行反序列化操作时, 如果其父类没有实现序列化接口 那么其父类的构造函数会被调用
36 */
37 }
38 }
39
40 /*
41 * 一个类实现了序列化接口,那么其子类都可以进行序列化
42 */
43 class Foo implements Serializable {
44 public Foo() {
45 System.out.println("foo...");
46 }
47 }
48
49 class Foo1 extends Foo {
50 public Foo1() {
51 System.out.println("foo1...");
52 }
53 }
54
55 class Foo2 extends Foo1 {
56 public Foo2() {
57 System.out.println("foo2...");
58 }
59 }
60
61 class Bar {
62 public Bar() {
63 System.out.println("bar");
64 }
65 }
66
67 class Bar1 extends Bar {
68 public Bar1() {
69 System.out.println("bar1..");
70 }
71 }
72
73 class Bar2 extends Bar1 implements Serializable {
74 public Bar2() {
75 System.out.println("bar2...");
76 }
77 }
博主原创,转载请注明出处,谢谢!