您的位置:首页 > 编程语言 > Java开发

java的序列化 和 反序列化总结---学习笔记

2016-03-22 15:54 435 查看
  java的序列化 和 反序列化

1、我们先看一下《java编程思想》第四版中对序列化定义

对象序列化
Java
1.1 增添了一种有趣的特性,名为“对象序列化”( Object Serialization)。它面向那些实现了

Serializable 接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可

通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在Windows 机器上创

建一个对象,对其序列化,然后通过网络发给一台 Unix 机器,然后在那里准确无误地重新“装配”。不必关

心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。

就其本身来说,对象的序列化是非常有趣的,因为利用它可以实现“有限持久化”。请记住“持久化”意味

着对象的“生存时间”并不取决于程序是否正在执行—— 它存在或“生存”于程序的每一次调用之间。通过

序列化一个对象,将其写入磁盘,以后在程序重新调用时重新恢复那个对象,就能圆满实现一种“持久”效

果。之所以称其为“有限”,是因为不能用某种“ persistent”(持久)关键字简单地地定义一个对象,并

让系统自动照看其他所有细节问题(尽管将来可能成为现实)。相反,必须在自己的程序中明确地序列化和

组装对象。

语言里增加了对象序列化的概念后,可提供对两种主要特性的支持。 Java 1.1 的“远程方法调用”( RMI)

使本来存在于其他机器的对象可以表现出好象就在本地机器上的行为。将消息发给远程对象时,需要通过对

象序列化来传输参数和返回值。 RMI 将在第 15 章作具体讨论。

对象的序列化也是 Java Beans 必需的,后者由 Java 1.1 引入。使用一个 Bean 时,它的状态信息通常在设计

期间配置好。程序启动以后,这种状态信息必须保存下来,以便程序启动以后恢复;具体工作由对象序列化

完成。

对象的序列化处理非常简单,只需对象实现了 Serializable 接口即可(该接口仅是一个标记,没有方法)。

在 Java 1.1 中,许多标准库类都发生了改变,以便能够序列化—— 其中包括用于基本数据类型的全部封装

器、所有集合类以及其他许多东西。甚至 Class 对象也可以序列化(第 11 章讲述了具体实现过程)。

为序列化一个对象,首先要创建某些 OutputStream 对象,然后将其封装到 ObjectOutputStream 对象内。此

时,只需调用 writeObject()即可完成对象的序列化,并将其发送给 OutputStream。相反的过程是将一个

InputStream 封装到 ObjectInputStream 内,然后调用 readObject()。和往常一样,我们最后获得的是指向

一个上溯造型 Object 的句柄,所以必须下溯造型,以便能够直接设置。

对象序列化特别“聪明”的一个地方是它不仅保存了对象的“全景图”,而且能追踪对象内包含的所有句柄

并保存那些对象;接着又能对每个对象内包含的句柄进行追踪;以此类推。我们有时将这种情况称为“对象

网”,单个对象可与之建立连接。而且它还包含了对象的句柄数组以及成员对象。若必须自行操纵一套对象

序列化机制,那么在代码里追踪所有这些链接时可能会显得非常麻烦。在另一方面,由于Java 对象的序列化

似乎找不出什么缺点,所以请尽量不要自己动手,让它用优化的算法自动维护整个对象网。

下面是序列化的小例子:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Person [name=" + name + "]";
}
public static void main(String[] args) {
Person p=new Person();
p.setName("张三");
try {

File f=new File("d:/3.txt");
if(!f.exists()){
f.createNewFile();
}

ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(f));

oos.writeObject(p);
oos.flush();
oos.close();

} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}

try {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:/3.txt"));

Person pp=(Person)ois.readObject();
System.out.println(pp);

} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}

}

}
结果为:
Person [name=张三]

从上面的代码中可以知道序列化对象和读取序列化对象过程中最要的两个函数是OutStream的writeObject和InputStream的readObject ,这个序列化过程是默认的。当然如果有特殊要求可以自己定制序列化。下面是书中例子

2、序列化的控制
正如大家看到的那样,默认的序列化机制并不难操纵。然而,假若有特殊要求又该怎么办呢?我们可能有特殊的安全问题,不希望对象的某一部分序列化;或者某一个子对象完全不必序列化,因为对象恢复以后,那一部分需要重新创建。此时,通过实现 Externalizable 接口,用它代替 Serializable 接口,便可控制序列化的具体过程。这个

Externalizable 接口扩展了 Serializable,并增添了两个方法: writeExternal()和 readExternal()。在序

列化和重新装配的过程中,会自动调用这两个方法,以便我们执行一些特殊操作。

下面这个例子展示了 Externalizable 接口方法的简单应用。注意 Blip1 和 Blip2 几乎完全一致,除了极微小

的差别(自己研究一下代码,看看是否能发现):
//: Blips.java
// Simple use of Externalizable & a pitfall
import java.io.*;
import java.util.*;

class Blip1 implements Externalizable {
    public Blip1() {
        System.out.println("Blip1 Constructor");
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("Blip1.writeExternal");
    }

    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        System.out.println("Blip1.readExternal");
    }
}

class Blip2 implements Externalizable {
    Blip2() {
        System.out.println("Blip2 Constructor");
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("Blip2.writeExternal");
    }

    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        System.out.println("Blip2.readExternal");
    }
}

public class Blips {
    public static void main(String[] args) {
        System.out.println("Constructing objects:");
        Blips b1 = new Blips();
        Blip2 b2 = new Blip2();
        try {
            ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
                    "Blips.out"));
            System.out.println("Saving objects:");
            o.writeObject(b1);
            o.writeObject(b2);
            o.close();
            // Now get them back:
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(
                    "Blips.out"));
            System.out.println("Recovering b1:");
            b1 = (Blips) in.readObject();
            // OOPS! Throws an exception:
            // ! System.out.println("Recovering b2:");
            // ! b2 = (Blip2)in.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
} // /:~


该程序输出如下:

Constructing objects:

Blip1 Constructor

Blip2 Constructor

Saving objects:

Blip1.writeExternal

Blip2.writeExternal

Recovering b1:

Blip1 Constructor

Blip1.readExternal

未恢复 Blip2 对象的原因是那样做会导致一个违例。你找出了 Blip1 和 Blip2 之间的区别吗? Blip1 的构建

器是“公共的”( public), Blip2 的构建器则不然,这样便会在恢复时造成违例。试试将 Blip2 的构建器

属性变成“ public”,然后删除//!注释标记,看看是否能得到正确的结果。

恢复 b1 后,会调用 Blip1 默认构建器。这与恢复一个 Serializable(可序列化)对象不同。在后者的情况

下,对象完全以它保存下来的二进制位为基础恢复,不存在构建器调用。而对一个Externalizable 对象,所

有普通的默认构建行为都会发生(包括在字段定义时的初始化),而且会调用readExternal()。必须注意这

一事实—— 特别注意所有默认的构建行为都会进行— — 否则很难在自己的 Externalizable 对象中产生正确的

行为。

下面这个例子揭示了保存和恢复一个 Externalizable 对象必须做的全部事情:

//: Blip3.java
// Reconstructing an externalizable object
import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

class Blip3 implements Externalizable {
int i;
String s; // No initialization

public Blip3() {
System.out.println("Blip3 Constructor");
// s, i not initialized
}

public Blip3(String x, int a) {
System.out.println("Blip3(String x, int a)");
s = x;
i = a;
// s & i initialized only in non-default
// constructor.
}

public String toString() {
return s + i;
}

public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Blip3.writeExternal");
// You must do this:
out.writeObject(s);
out.writeInt(i);
}

public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
System.out.println("Blip3.readExternal");
// You must do this:
s = (String) in.readObject();
i = in.readInt();
}

public static void main(String[] args) {
System.out.println("Constructing objects:");
Blip3 b3 = new Blip3("A String ", 47);
System.out.println(b3.toString());
try {
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
"Blip3.out"));
System.out.println("Saving object:");
o.writeObject(b3);
o.close();
// Now get it back:
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
"Blip3.out"));
System.out.println("Recovering b3:");
b3 = (Blip3) in.readObject();
System.out.println(b3.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
} // /:~


其中,字段 s 和 i 只在第二个构建器中初始化,不关默认构建器的事。这意味着假如不在readExternal 中初

始化 s 和 i,它们就会成为 null(因为在对象创建的第一步中已将对象的存储空间清除为 1)。若注释掉跟

随于“ You must do this”后面的两行代码,并运行程序,就会发现当对象恢复以后, s 是 null,而 i 是

零。

若从一个 Externalizable 对象继承,通常需要调用 writeExternal()和 readExternal()的基础类版本,以便

正确地保存和恢复基础类组件。

所以为了让一切正常运作起来,千万不可仅在 writeExternal()方法执行期间写入对象的重要数据(没有默

认的行为可用来为一个 Externalizable 对象写入所有成员对象)的,而是必须在 readExternal()方法中也

恢复那些数据。初次操作时可能会有些不习惯,因为Externalizable 对象的默认构建行为使其看起来似乎正

在进行某种存储与恢复操作。但实情并非如此。
3、transient(临时)关键字

控制序列化过程时,可能有一个特定的子对象不愿让Java 的序列化机制自动保存与恢复。一般地,若那个子

对象包含了不想序列化的敏感信息(如密码),就会面临这种情况。即使那种信息在对象中具有“ private”

(私有)属性,但一旦经序列化处理,人们就可以通过读取一个文件,或者拦截网络传输得到它。

为防止对象的敏感部分被序列化,一个办法是将自己的类实现为Externalizable,就象前面展示的那样。这

样一来,没有任何东西可以自动序列化,只能在writeExternal()明确序列化那些需要的部分。

然而,若操作的是一个 Serializable 对象,所有序列化操作都会自动进行。为解决这个问题,可以用

transient(临时) 逐个字段地关闭序列化,它的意思是“不要麻烦你(指自动机制)保存或恢复它了—— 我

会自己处理的”。

例如,假设一个 Login 对象包含了与一个特定的登录会话有关的信息。校验登录的合法性时,一般都想将数

据保存下来,但不包括密码。为做到这一点,最简单的办法是实现Serializable,并将 password 字段设为

transient。有时候需要序列化的对象中的属性包含有不能序列化的对象。例如下面的例子:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private Student student;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}

@Override
public String toString() {
return "Person [name=" + name + ", student=" + student + "]";
}
public static void main(String[] args) {
Person p=new Person();
p.setStudent(new Student("张三","4岁"));
p.setName("学生");
try {

File f=new File("d:/3.txt");
if(!f.exists()){
f.createNewFile();
}

ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(f));

oos.writeObject(p);
oos.flush();
oos.close();

} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}

try {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:/3.txt"));

Person pp=(Person)ois.readObject();
System.out.println(pp);

} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}

}

}

class Student {

private String name;
private String age;

public Student() {
super();
// TODO Auto-generated constructor stub
}

public Student(String name, String age) {
super();
this.name = name;
this.age = age;
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}

}


执行结果报错:
java.io.NotSerializableException: Student

这是由于Student 类不可以序列化, 在这种情况下,可以给Student 添加 transient 关键字,

    private transient Student student;

执行结果:

Person [name=学生, student=null]

如果Student是可以序列化的,Student类实现Serializable,去掉transient
关键字:

执行结果:

Person [name=学生, student=Student [name=张三, age=4岁]]

4 总结
序列化:将java对象转换为字节序列的过程叫做序列化

反序列化:将字节对象转换为java对象的过程叫做反序列化

通过实现 Externalizable 接口,用它代替 Serializable 接口,便可控制序列化的具体过程,这个

Externalizable 接口扩展了 Serializable,并增添了两个方法: writeExternal()和 readExternal()。

transient 可以使某些属性不被序列化,但是如果是实现了Externalizable接口的类中属性添加 transient也是会被序列化的.

本文参考了<java编程思想>第四版 第10章第九节
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: