什么是序列化
序列化(serialization)是把一个对象的状态写入 一个字节流的过程。当你想要把你的程序状态存 到一个固定的存储区域例如文件时,它是很管用 的。稍后一点时间,你就可以运用序列化过程存 储这些对象将一个对象保存到永久存储设备上(如:文件)称为持久化。(程序储存到文件)
一、为什么需要序列化?
第一种情况是:一般情况下Java对象的声明周期都比Java虚拟机的要短,实际应用中我们希望在JVM停止运行之后能够持久化指定的对象,这时候就需要把对象进行序列化之后保存。
第二种情况是:需要把Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。
注意事项
假设一个被序列化的对象引用了其他对象,同样, 其他对象又引用了更多的对象。这一系列的对象 和它们的关系形成了一个顺序图表。在这个对象 图表中也有循环引用。也就是说,对象X可以含 有一个对象Y的引用,对象Y同样可以包含一个对 象X的引用。对象同样可以包含它们自己的引用。 对象序列化和反序列化的工具被设计出来并在这 一假定条件下运行良好。如果你试图序列化一个 对象图表中顶层的对象,所有的其他的引用对象 都被循环的定位和序列化。
同样,在反序列化过 程中,所有的这些对象以及它们的引用都被正确 的恢复( 在序列化时,static变量是无法序列化的;如果A包含了
对B的引用,那么在序列化A的时候也会将B一并地序列化;如果此时A可以序列化,B无法序列化,那么当序列化A的时候就会发生异常,这时就需要将对B的引用设为transient,该关键字表示变量不会被序列化)
当一个对象被序列化时,只保存对象的非静态成员变量,不能保存任何的成员方法和静态的成员变量。 如果一个对象的成员变量是一个对象,那么这个 对象的数据成员也会被保存。如果一个可序列化的对象包含对某个不可序列化 的对象的引用,那么整个序列化操作将会失败, 并且会抛出一个NotSerializableException。我们可以将这个引用标记为transient,那么对象仍 然可以序列化,声明成transient的变量不被序列化工具存储。 同样,static变量也不被存储
二、Serializable接口
实现Serializable接口的对象可以被 序列化工具存储和恢复。Serializable接口没 有定义任何成员。它只用来表示一个类可以被 序列化。如果一个类可以序列化,它的所有子 类都可以序列化。一个类若想被序列化,则需要实现java.io.Serializable接口,该接口中没有定义任何方法,是一个标识性接口(Marker Interface),当一个类实现了该接口,就表示这个类的对象是可以序列化的
案例:反序列化时不会调用对象的任何构造方法,仅仅根据所保存的对象状态信息,在内存中重新构建对象
class Person2 implements Serializable{
int age;
String name;
double height;
public Person2(int age,String name,double height){
this.age=age;
this.name=name;
this.height=height;
}
}
public class SerializableTest2 {
public static void main(String[]args)throws Exception{
Person2 p1 = new Person2(20,"zhangsan",4.55);
//开始序列化
FileOutputStreamf os = new FileOutputStream("Person2.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(p1);
oos.close();
//开始反序列化
FileInputStreamf is = new FileInputStream("Person2.txt");
ObjectInputStream ois =new ObjectInputStream(fis);
Person2 p= (Person2)ois.readObject();
System.out.println(p.age+","+p.name+","+p.height);20,zhangsan,4.55
ois.close();
}
}
问题1、类添加序列化的ID作用
Java常用于开发分布式应用,分布式应用就涉及到部署的主机的物理位置是不同的,主机应用之间的交互涉及到信息在网络传输的问题,或者应用内部的信息也需要存储到磁盘中;不管是信息的存储或者传输,都是以二进制流的方式进行的。Java对象序列化机制就是把内存中的Java对象(User之类的JavaBean)转换成二进制流。java对象序列化后可以很方便的存储或者在网络中传输。Java的序列化机制是通过运行时判断类的序列化ID(serialVersionUID)来判定版本的一致性。在反序列化时,java虚拟机会通过二进制流中的serialVersionUID与本地的对应的实体类进行比较,如果相同就认为是一致的,可以进行反序列化,正确获得信息,否则抛出序列化版本不一致的异常。所以涉及到数据传输或者存储的类,严格意义上来说都要加上序列化ID,这也是一种良好的编程习惯。纯手打~
三、按自己的想法序列化
1、实现writeObject和readObject
在序列化和反序列化进程中需要特殊处理 的 Serializable 类应该实现以下方法:
private void writeObject(java.io.ObjectOutputStream stream) throws IOException; private void
private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException;
//这两个方法不属于任何一个类和任何一个接口,是非常特 殊的方法.
当我们在一个待序列化/反序列化的类中实现了以上两个private方法(方法声明要与上面的保持完全的一致),那么就允许我们以更
加底层、更加细粒度的方式控制序列化/反序列化的过程。
当我们在上面的案例加入上面2个方法时效果如下
class Person2 implements Serializable{
int age;
String name;
double height;
public Person2(int age,String name,double height){
this.age=age;
this.name=name;
this.height=height;
}
private void writeObject(java.io.ObjectOutputStreamout)throws IOException{
System.out.println("writeobject");
}
privatevoidreadObject(java.io.ObjectInputStreamin)throwsIOException,ClassNotFoundException{
System.out.println("readobject");
}
}
public class SerializableTest2{
public static void main(String[] args)throws Exception{
Person2 p1 = new Person2(20,"zhangsan",4.55);
FileOutputStreamf os=newFileOutputStream("Person2.txt");
ObjectOutputStream oos = newObjectOutputStream(fos);
oos.writeObject(p1);//writeobject
oos.close();
FileInputStream fis=new FileInputStream("Person2.txt");
ObjectInputStream ois=new ObjectInputStream(fis);
Person2 p = (Person2)ois.readObject();//readobject
System.out.println(p.age+","+p.name+","+p.height);//0,null,0.0
ois.close();
}
}
为什么 person的属性是null 和0呢?
现在我们将俩个private方法改如下例
private void writeObject(java.io.ObjectOutputStreamout)throws IOException{
out.writeInt(age);
out.writeUTF(name);
}
private void readObject(java.io.ObjectInputStreamin) throws IOException,ClassNotFoundException{
age = in.readInt();
name =in.readUTF();
}
//输出如下
//20,zhangsan,0.0
这说明了这两个方法可以帮我们选的我们想要的属性
2、实现Externalizable接口
使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象
的字段的值分别填充到新对象中。这就是为什么输出结果中会显示调动了无参构造器。由于这个原因,实现Externalizable接
口的类必须要提供一个无参的构造器,且它的访问权限为public。 Serializable没这个限制
Externalizable 接口定义了两个方法:
//inStream是对象被读取的字节流,outStream是 对象被写入的字节流。
void readExternal(ObjectInput inStream) throws IOException, ClassNotFoundException
void writeExternal(ObjectOutput outStream) throws IOException
Externalizable 实例类的惟一特性是可以 被写入序列化流中,该类负责保存和恢复 实例内容。 若某类要完全控制某一对象及 其超类型的流格式和内容,则它要实现 Externalizable 接口的 writeExternal 和 readExternal 方法。这些方法必须显式与 超类型进行协调以保存其状态。这些方法 将代替自定义的 writeObject 和 readObject 方法实现
public class Blip3 implements Externalizable{
private int i;
private String s;//Noinitialization
public Blip3 (){
System.out.println("Blip3Constructor");
}
public Blip3 (Stringx,inta){
System.out.println("Blip3(Stringx,inta)");
s=x;
i=a;
}
public String toString(){returns+i;}
public void writeExternal(ObjectOutput out)throws IOException{
System.out.println("Blip3.writeExternal");
out.writeObject(s);
out.writeInt(i);
}
public void readExternal(ObjectInput in)throws IOException,ClassNotFoundException{
System.out.println("Blip3.readExternal");
s=(String)in.readObject();
i=in.readInt();
}
public static void main(String[] args)throws IOException,ClassNotFoundException{
System.out.println("Constructingobjects:");
Blip3b3=newBlip3("AString",47);
System.out.println(b3);
ObjectOutputStreamo=newObjectOutputStream(newFileOutputStream("Blip3.out"));
System.out.println("Savingobject:");
o.writeObject(b3);
o.close();
ObjectInputStreamin=newObjectInputStream(newFileInputStream("Blip3.out"));
System.out.println("Recoveringb3:");
b3=(Blip3)in.readObject();
System.out.println(b3);
}
}
输出
Constructingobjects:
Blip3(Stringx,inta)
AString47
Savingobject:
Blip3.writeExternal
Recoveringb3:
Blip3Constructor
Blip3.readExternal
AString47
3、transient
将我们认为不应该序列化的非静态属性加上transient关键字,表示这个属性不能序列化,transient关键字只能修饰变量,而不能修饰方法和类