JAVA序列化机制学习札记

来源:互联网 发布:山东师范大学网络平台 编辑:程序博客网 时间:2024/05/29 19:08

一、什么是序列化

java中的序列化(serialization)机制能够将一个实例对象的状态信息写入到一个字节流中,使其可以通过socket进行传输、或者持久化存储到数据库或文件系统中;然后在需要的时候,可以根据字节流中的信息来重构一个相同的对象。序列化机制在java中有着广泛的应用,EJBRMI等技术都是以此为基础的。


二、序列化机制实例

一般而言,要使得一个类可以序列化,只需简单实现java.io.Serializable接口即可。该接口是一个标记式接口,它本身不包含任何内容,实现了该接口则表示这个类准备支持序列化的功能。如下例定义了类Person,并声明其可以序列化。

 

例一:

 

Java代码

1.   public class Person implements java.io.Serializable {}  

public class Person implements java.io.Serializable {}

序列化机制是通过java.io.ObjectOutputStream类和java.io.ObjectInputStream类来实现的。在序列化(serialize)一个对象的时候,会先实例化一个ObjectOutputStream对象,然后调用其writeObject()方法;在反序列化(deserialize)的时候,则会实例化一个ObjectInputStream对象,然后调用其readObject()方法。下例说明了这一过程。

Java代码

1.   public void serializeObject(){   

2.        String fileName = "ser.out";   

3.        FileOutputStream fos = new FileOutputStream(fileName);   

4.        ObjectOutputStream oos = new ObjectOutputStream(fos);   

5.        oos.writeObject(new Person());   

6.        oos.flush();   

7.   }   

8.     

9.   public void deserializeObject(){   

10.       String fileName = "ser.out";   

11.       FileInputStream fos = new FileInputStream(fileName);   

12.       ObjectInputStream oos = new ObjectInputStream(fos);   

13.       Person p = oos.readObject();   

14.  }  

public void serializeObject(){

     String fileName = "ser.out";

     FileOutputStream fos = new FileOutputStream(fileName);

     ObjectOutputStream oos = new ObjectOutputStream(fos);

     oos.writeObject(new Person());

     oos.flush();

}

 

public void deserializeObject(){

     String fileName = "ser.out";

     FileInputStream fos = new FileInputStream(fileName);

     ObjectInputStream oos = new ObjectInputStream(fos);

     Person p = oos.readObject();

}

上例中我们对一个Person对象定义了序列化和反序列化的操作。但如果Person类是不能序列化的话,即对不能序列化的类进行序列化操作,则会抛出 java.io.NotSerializableException异常。
JVM
中有一个预定义的序列化实现机制,即默认调用
ObjectOutputStream.defaultWriteObject() ObjectInputStream.defaultReadObject() 来执行序列化操作。如果想自定义序列化的实现,则必须在声明了可序列化的类中实现 writeObject()readObject()方法。

 

例二:

 

《实现类的序列化--例子将Vector对象压入标准流》出处 CN-JAVA原创:jackliu

Vector
对象是一个很灵活的java数据结构,在实际编程中,有时需要我们将一个Vector对象传递给另一个Java程序并保持Vector的数据结构状态,这时,我们可以将需要传递的对象实现java.io.Serializable接口,序列化这个类,由于序列化本身就是允许一个对象在虚拟机之间传送(或者经过一段空间,如在RMI;或者经过一段时间,比如数据流被保存到一个文件中)。 关于类的序列化请查阅相关资料,本篇不在叙述,下面使用一个简单的程序说明如何把一个Vector对象序列化并放到一个流中(为了测试方便,这里放到一个文件流中,如果换成一个套接字就可以把对象发送给远程请求者)

 

程序1:把一个Vector对象存储到一个testvector.obj文件里(模拟server socket处理)

 1 import java.io.*;
 2 import java.util.*;
 3
 4 class TestVector implements java.io.Serializable {
 5         private Vector vect=new Vector();
 6         
 7         publicvoid add(Object obj) {
 8                 this.vect.add(obj);
 9         }
10         public void print() {
11                 System.out.println(this.vect);
12         }
13         public static void main(String[] args) {
14                 try  {
15                         /**//* 将对象写到一个文件里 */
16                         FileOutputStream objfile = new FileOutputStream("testvector.obj");
17                         /**//* 创建一个输出流 */
18                         ObjectOutputStream p = new ObjectOutputStream(objfile);
19                         /**//* 创建一个TestVector对象 */
20                         TestVector tv =new TestVector();
21                         /**//*Vector写入几String个对象*/
22                         tv.add("One");
23                         tv.add("Two");
24                         tv.add("Three");
25                         p.writeObject(tv); // tv写入流
26                         p.flush();
27                         objfile.close();    // 关闭文件对象
28                 } catch (Exception e)  {
29                         e.printStackTrace();
30                 }
31         }
32 }
33

编译程序1,运行后,在当前目录生成一个testvector.obj文件,这个文件里存放了TestVector类的数据


程序2:从testvector.obj文件里获取TestVector对象(模拟socke客户端)

 1 import java.io.*;
 2 import java.util.*;
 3
 4 class ReadVectorObj {
 5         public static void  main (String[] args) {
 6                 try  {
 7                         /**//* 打开文件,读取Vector 存放的对象数据 */
 8                         FileInputStream objfile = new FileInputStream("testvector.obj");
 9                         ObjectInputStream q = new ObjectInputStream(objfile);
10                         /**//* 获取对象原始数据 */
11                         TestVector myvector = (TestVector)q.readObject();
12                         myvector.print(); 
13                 } catch (Exception e)  {
14                         e.printStackTrace();   
15                 }
16         }
17 }

编译程序2,运行后,读取testvector.obj文件,根据内容构建出原始的TestVector类。



三、JAVA序列化机制的几种使用情况

 

一般在序列化一个类A的时候,有以下三种情况:

·  A没有父类,自己实现了Serializable接口

·  A有父类B,且父类实现了Serializable接口

·  A有父类B,但父类没有实现Serializable接口

对于第一种情况,直接实现Serializable接口即可。
对于第二种情况,因为父类B已经实现了Serializable接口,故类A无需实现此接口;如果父类实现了writeObject()readObject(),则使用此方法,否则直接使用默认的机制。
对于第三种情况,则必须在类A中显示实现writeObject()readObject()方法来处理父类B的状态信息;还有一点要特别注意,在父类B中一定要有一个无参的构造函数,这是因为在反序列化的过程中并不会使用声明为可序列化的类A的任何构造函数,而是会调用其没有申明为可序列化的父类B的无参构造函数。

 

 

四、JAVA序列化机制使用详解

 

4.1 处理对象流:

(序列化过程和反序列化过程)

java.io包有两个序列化对象的类。ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象。
    
我们先了解ObjectOutputStream类吧。ObjectOutputStream类扩展DataOutput接口。
writeObject()
方法是最重要的方法,用于对象序列化。如果对象包含其他对象的引用,则writeObject()方法递归序列化这些对象。每个ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。(这点很重要)由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。
下面,让我们从例子中来了解ObjectOutputStream这个类吧。

1.   // 序列化 today's date 到一个文件中.

2.       FileOutputStream f = new FileOutputStream("tmp");

3.       ObjectOutputStream s = new ObjectOutputStream(f);

4.       s.writeObject("Today");

5.       s.writeObject(new Date());

6.       s.flush();

   现在,让我们来了解ObjectInputStream这个类。它与ObjectOutputStream相似。它扩展DataInput接口。ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。
例子如下:

1.   //从文件中反序列化 string 对象和 date 对象

2.       FileInputStream in = new FileInputStream("tmp");

3.       ObjectInputStream s = new ObjectInputStream(in);

4.       String today = (String)s.readObject();

5.       Date date = (Date)s.readObject();

4.2 定制序列化过程:

序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为serializable,但仍可手工控制声明为statictransient的数据成员。
例子:一个非常简单的序列化类。

1.   public class simpleSerializableClass implements Serializable{

2.       String sToday="Today:";

3.       transient Date dtToday=new Date();

4.   }

序列化时,类的所有数据成员应可序列化除了声明为transientstatic的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化statictransient。我们的类要用writeObject()readObject()方法以处理这些数据成员。使用writeObject()readObject()方法时,还要注意按写入的顺序读取这些数据成员。
关于如何使用定制序列化的部分代码如下:

1.   //重写writeObject()方法以便处理transient的成员。

2.   public void writeObject(ObjectOutputStream outputStream) throws IOException{

3.       outputStream.defaultWriteObject();//使定制的writeObject()方法可以

4.                           利用自动序列化中内置的逻辑。

5.       outputStream.writeObject(oSocket.getInetAddress());

6.       outputStream.writeInt(oSocket.getPort());

7.   }

8.   //重写readObject()方法以便接收transient的成员。

9.   private void readObject(ObjectInputStream inputStream) throws IOException,ClassNotFoundException{

10.      inputStream.defaultReadObject();//defaultReadObject()补充自动序列化

11.      InetAddress oAddress=(InetAddress)inputStream.readObject();

12.      int iPort =inputStream.readInt();

13.      oSocket = new Socket(oAddress,iPort);

14.      iID=getID();

15.      dtToday =new Date();

16.  }

4.3 完全定制序列化过程:

如果一个类要完全负责自己的序列化,则实现Externalizable接口而不是Serializable接口。Externalizable接口定义包括两个方法writeExternal()readExternal()。利用这些方法可以控制对象数据成员如何写入字节流.类实现Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现Externalizable接口会有重大的安全风险。writeExternal()readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。到此为至,我们学习了序列化的基础部分知识。关于序
列化的高级教程,以后再述。


五、序列化机制的一些问题

 

·         性能问题为了序列化类A一个实例对象,所需保存的全部信息如下:
1.
与此实例对象相关的全部类的元数据(metadata)信息;因为继承关系,类A的实例对象也是其任一父类的对象。因而,需要将整个继承链上的每一个类的元数据信息,按照从父到子的顺序依次保存起来。
2.
A的描述信息。此描述信息中可能包含有如下这些信息:类的版本ID(version ID)、表示是否自定义了序列化实现机制的标志、可序列化的属性的数目、每个属性的名字和值、及其可序列化的父类的描述信息。
3.
将实例对象作为其每一个超类的实例对象,并将这些数据信息都保存起来。
RMI等远程调用的应用中,每调用一个方法,都需要传递如此多的信息量;久而久之,会对系统的性能照成很大的影响。

·         版本信息当用readObject()方法读取一个序列化对象的byte流信息时,会从中得到所有相关类的描述信息以及示例对象的状态数据;然后将此描述信息与其本地要构造的类的描述信息进行比较,如果相同则会创建一个新的实例并恢复其状态,否则会抛出异常。这就是序列化对象的版本检测。JVM中默认的描述信息是使用一个长整型的哈希码(hashcode)值来表示,这个值与类的各个方面的信息有关,如类名、类修饰符、所实现的接口名、方法和构造函数的信息、属性的信息等。因而,一个类作一些微小的变动都有可能导致不同的哈希码值。例如开始对一个实例对象进行了序列化,接着对类增加了一个方法,或者更改了某个属性的名称,当再想根据序列化信息来重构以前那个对象的时候,此时两个类的版本信息已经不匹配,不可能再恢复此对象的状态了。要解决这个问题,可能在类中显示定义一个值,如下所示:

Java代码

1.   private static final long serialVersionUID = ALongValue;  

private static final long serialVersionUID = ALongValue;


这样,序列化机制会使用这个值来作为类的版本标识符,从而可以解决不兼容的问题。但是它却引入了一个新的问题,即使一个类作了实质性的改变,如增加或删除了一些可序列化的属性,在这种机制下仍然会认为这两个类是相等的。


 

六、另外一种选择

作为实现Serializable接口的一种替代方案,实现java.io.Externalizable接口同样可以标识一个类为可序列化。
Externalizable
接口中定义了以下两个方法:

Java代码

1.   public void readExternal(ObjectInput in);   

2.   public void writeExternal(ObjectOutput out);  

public void readExternal(ObjectInput in);

public void writeExternal(ObjectOutput out);


这两个方法的功能与 readObject()writeObject()方法相同,任何实现了Externalizable接口的类都需要这实现两个函数来定义其序列化机制。
使用Externalizable比使用Serializable有着性能上的提高。前者序列化一个对象,所需保存的信息比后者要小,对于后者所需保存的第3个方面的信息,前者不需要访问每一个父类并使其保存相关的状态信息,而只需简单地调用类中实现的writeExternal()方法即可。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 两手机互换号后微信怎么办 手机申请不了qq怎么办 快手账号保护了怎么办 qq号过期登不上怎么办 xp 登陆界面不见了怎么办 手机号qq 别人登录怎么办 qq号申请频繁怎么办 qq号实名验证怎么办 陌陌没法注册怎么办 腾讯q币充值错误怎么办 手机号码注册微信号怎么办 q币充错了号怎么办 微信超额度提现都不可以怎么办 微信发红包转账限额怎么办 银行卡没设密码怎么办 宜人贷逾期一天怎么办 快手实名已认证怎么办 爱奇艺充会员没有银行卡怎么办 手机qq内存太大怎么办 借呗逾期半年怎么办 支付宝支付错误怎么办 小米6支付宝闪退怎么办 验证码发送失败怎么办 支付宝升级后打不开怎么办 steam支付宝失败怎么办 支付宝无响应怎么办 花呗加载失败怎么办 吃鸡平台无效怎么办 悦支付登录不了怎么办 微信里的钱超额怎么办 qq不能发红包怎么办 qq红包多发了怎么办 qq红包领不了怎么办 qq红包密码忘记怎么办 钱包锁密码忘记怎么办 支付宝红包过期怎么办 qq红包无法领取怎么办 qq红包未领取怎么办 支付宝转账超时怎么办 电脑显示没信号怎么办 陌生人打QQ电话怎么办