序列化
Java提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中的数据的类型。
序列化存储数据的优点
序列化的文件更容易让程序回复到原来的状态,同时序列化的文件比较安全,因为它不是原本的内容,而是经过了编码处理的。序列化的对象方便在JVM之间转移,这些JVM可以是同一台计算机的,也可以是远程的。
使用序列化的场合
如果需要该对象在网上传送,或保存在磁盘上,以便以后再某个时刻再访问,这种情况便可将其序列化。如果对象的内部状态不适合输出到二级存储设备(硬盘)或通过网络传送,就需要谨慎使用序列化。使用对象序列化是为了支持两种特性:Java的远程方法调用和Java Bean。
Java中序列化的实现
Java中实现序列化和反序列化的类分别是ObjectOutputStream和ObjectInputStream。
ObjectOutputStream序列化方法:
public final void writeObject(Object x) throws IOException
ObjectInputStream反序列方法:
public final Object readObject() throws IOException,ClassNotFoundException
该方法从流中取出下一个对象,并将对象反序列化。它的返回值是Object,因此,你需要将它转换成合适的数据类型。
代码举例:
Employee类
package JavaReview.FileIO;
import java.io.Serializable;
public class Employee implements Serializable {
public String name;
public String address;
public transient int SSN;
public int number;
public Task ta;
public Employee(String name, String address, int SSN, int number, Task ta) {
this.name = name;
this.address = address;
this.SSN = SSN;
this.number = number;
this.ta = new Task(ta);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", SSN=" + SSN +
", number=" + number +
", ta=" + ta.toString() +
'}';
}
}
class Task implements Serializable{
public String taskName;
public String taskContent;
public Task(Task ta) {
this.taskName = ta.taskName;
this.taskContent = ta.taskContent;
}
public Task(String taskName,String taskContent ){
this.taskContent=taskContent;
this.taskName=taskName;
}
@Override
public String toString() {
return "Task{" +
"taskName='" + taskName + '\'' +
", taskContent='" + taskContent + '\'' +
'}';
}
}
实现序列化和反序列化
package JavaReview.FileIO;
import java.io.*;
public class SerializeDemo {
public static void main(String[] args) {
Task ta=new Task("项目XXX","完成XXX");
Employee ee=new Employee("张三","中国北京市",12312,111,ta);
Employee ee2=new Employee("李四","中国上海市",122222,222,ta);
try {
//开始序列化对象ee,ee2
FileOutputStream outfileStream=new FileOutputStream("example.ser");
ObjectOutputStream os=new ObjectOutputStream(outfileStream);
os.writeObject(ee);
os.writeObject(ee2);
os.close();
outfileStream.close();
System.out.println("写入文件成功");
//开始反序列化对象ee,ee2
FileInputStream infileStream=new FileInputStream("example.ser");
ObjectInputStream is=new ObjectInputStream(infileStream);
Employee e=null;
Employee e2=null;
e=(Employee) is.readObject();
e2=(Employee) is.readObject();
System.out.println(e.toString());
System.out.println(e2.toString());
infileStream.close();
is.close();
}catch (IOException e) {
e.printStackTrace();
}catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
}
运行结果
写入文件成功
Employee{name='张三', address='中国北京市', SSN=0, number=111, ta=Task{taskName='项目XXX', taskContent='完成XXX'}}
Employee{name='李四', address='中国上海市', SSN=0, number=222, ta=Task{taskName='项目XXX', taskContent='完成XXX'}}
一个类的对象要想序列化成功,必须满足两个条件:
- 该类必须实现 java.io.Serializable 对象,Serializable作为接口,并没有任何方法需要实现,它的唯一作用就是声明有实现它的类是可以被序列化的 。
- 该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须用transient关键词注明是短暂的。
上述例子中,当对象被序列化时,属性 SSN 的值为 111222333,但是因为该属性是短暂的,该值没有被发送到输出流。所以反序列化后 Employee 对象的 SSN 属性为 0。同时序列化对象中存在一个对象引用ta,ta所在的类也是继承了Serializable 接口的,所以ta变量本身以及ta所指向的对象内容也是能够且会被序列化的。如果把Task的接口继承删掉,就会报出没有继承接口的错误。如下:
java.io.NotSerializableException: JavaReview.FileIO.Task
下列特地说明几点:
-
序列化和反序列化都需要放到try/catch中进行操作。反序列化要考虑读取的文件不存在的情况,所以需要添加ClassNotFoundException异常。
-
FileOutputStream/FileInputStream能够将字节写入/读出文件中,但是我们通常不会直接写字节,而是以对象层次的观点来写入,所以需要高层的连接串流,类ObjectInputStream
和 ObjectOutputStream 就是高层次的数据流,能够将对象转变成字节流。 -
有些变量是不能被序列化的。比如这个数据是动态的,即只有在执行时求出而不能或不必存储,他们可能在执行期当场创建才有意义,一旦程序关闭之后,联机本身就不在只用,下次执行时需要重新创建出来。
-
如果对象在继承树上有个不可序列化的祖先类,则该不可序列化类以及在它之上的类的构造函数(就算是可序列化也一样)就会执行。一旦构造函数连锁启动之后将无法停止,也就是说,从第一个不可序列化的父类开始,全部都会重新初始化状态。
-
transient变量会被赋值null的对象引用或primitive主数据类型的默认为0、false等值。
-
反序列化的时候,新的对象会被配置在堆上,但是构造函数不会执行。因为是要返回存储时的状态,所以如果执行构造函数则是变成了一个全新的状态。
-
static变量不会被序列化,因为static是类变量,而不是对象变量,即对象的存储不会影响它的值。
-
反序列化时,读取对象的顺序与写入的顺序相同。
-
对象序列化的一个限制就是它只能解决Java的解决方案,只有Java程序才能给这个对象去序列化。一种更具有互操作性的解决方案是把数据转换成XML格式,这样就能够被各种各样的平台和语言使用。
序列化优化
如果需要读写大量特定的类对象,就应该考虑使用E接口。
Externalizable 实例类的唯一特性是可以被写入序列化流中,该类负责保存和恢复实例内容。 若某个要完全控制某一对象及其超类型的流格式和内容,则它要实现 Externalizable 接口的 writeExternal 和 readExternal 方法。这些方法必须显式与超类型进行协调以保存其状态。这些方法将代替定制的 writeObject 和 readObject 方法实现。
Serialization 对象将使用 Serializable 和 Externalizable 接口。对象持久性机制也可以使用它们。要存储的每个对象都需要检测是否支持 Externalizable 接口。如果对象支持 Externalizable,则调用 writeExternal 方法。如果对象不支持 Externalizable 但实现了 Serializable,则使用 ObjectOutputStream 保存该对象。
使用Externalizable 接口的要求:
- 必须实现该接口
- 必须实现writeExternal 方法,以便保存对象的状态。而且它必须显示地与它的上层类协作以保存其状态。
- 必须实现readExternal 方法,以便从流中读取writeExternal 方法写入的数据,回复对象的状态。而且它必须显示地与它的上层类协作以保存其状态。
参考资料:http://www.runoob.com/java/java-serialization.html
参考资料:《Head First Java》