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方法进行反序列化。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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();<br>            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是不会被序列化的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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 +  " ]" ;
     }
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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.

我们来看另外一种情况:

1
2
3
4
5
6
7
8
class  implements  Serializable{
     B b;
     //...
}
 
class  implements  Serializable{
     //...
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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)
      ....

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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)优化序列化的性能(之前的那个例子已经解释了这种情况)

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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;  // 注意此处的transient
 
     public  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 );
     }
 
     @Override
     public  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()方法在单例模式中的使用:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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)。

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

例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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( "调用有参构造函数" );
     }
 
     @Override
     public  void  writeExternal(ObjectOutput out)  throws  IOException {
         out.writeObject(name);
     }
 
     @Override
     public  void  readExternal(ObjectInput in)  throws  IOException,
             ClassNotFoundException {
         name = (String) in.readObject();
     }
 
     @Override
     public  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]

目录
相关文章
|
算法 测试技术 C++
C++算法:戳印序列原理及实现方法一
C++算法:戳印序列原理及实现方法一
|
6月前
|
Go Java C++
Java每日一练(20230411) 同构字符串、随机字符串、交错字符串
Java每日一练(20230411) 同构字符串、随机字符串、交错字符串
40 0
Java每日一练(20230411) 同构字符串、随机字符串、交错字符串
|
存储 编解码 算法
C++算法:二叉树的序列化与反序列化
C++算法:二叉树的序列化与反序列化
|
存储 Java 数据安全/隐私保护
java实现稀疏数组
稀疏数组是一种为了节约存储空间而产生的数据结构,本质上稀疏数组就是一个普通的二维数组。其实在真实的应用中,稀疏数组的用武之地很少,起码笔者工作了几年,是没有发现稀疏数组的用武之地的(感觉游戏领域可能会用到),但是作为数据结构的一种,我们学习他还是可以得到一些启发的,比如使用时间换空间的思想,反过来使用空间换时间不也是可以的吗。所以学习他不一定非要用,但是肯定会对自己的思维有帮助。
99 0
java实现稀疏数组
|
人工智能 文字识别 算法
数据结构和算法-原始数组转稀疏数组(一)|学习笔记
快速学习数据结构和算法-原始数组转稀疏数组(一)
数据结构和算法-原始数组转稀疏数组(一)|学习笔记
|
缓存 移动开发 算法
数据结构和算法-原始数组转稀疏数组(二)|学习笔记
快速学习数据结构和算法-原始数组转稀疏数组(二)
数据结构和算法-原始数组转稀疏数组(二)|学习笔记
|
Java
Java经典编程习题100例:第18例:编写程序,将一个数组中的元素倒排过来。例如原数组为1,2,3,4,5;则倒排后数组中的值
Java经典编程习题100例:第18例:编写程序,将一个数组中的元素倒排过来。例如原数组为1,2,3,4,5;则倒排后数组中的值
247 0
|
Java 调度
序列化和编码的不同点
序列化和编码的不同点
232 0
序列化和编码的不同点
|
Java
使用栈解决分隔符匹配问题(java实现)
编写判断Java语句中分隔符是否匹配的问题。
124 0
|
存储 Python
Python实现数据序列和反序列化
python的pickle模块实现了基本的数据序列和反序列化。 通过pickle模块的序列化操作我们能够将程序中运行的对象信息保存到文件中去,永久存储。 通过pickle模块的反序列化操作,我们能够从文件中创建上一次程序保存的对象。
49 0
下一篇
无影云桌面