首先我们知道对象都是存放在堆内存中的,一旦使用完毕不在被引用之后就会被垃圾回收机制回收,或则退出程序的时候就会被释放,所以要想持久的保存对象,而持久的保存则当然要保存到我们的存储设备上,要完成这样一个动作,那就要靠对象的序列化来完成了,接下来来看一下关于序列化的一些定义吧。。。。。。
1. 序列化:将对象读取到字节流中并写入保存起来,并在以后还原这个对象,这种机制叫做对象序列化,是一个把对象的状态写入一个字节流的过程(注意,不是其他的字符流什么的),当你想要把你的程序状态存到一个固定的存储区域,例如文件时,它就能发挥他的作用了,之后就可以运用序列化过程存储这些对象,并可以永久的保存。
2. 序列化的目的:就我目前对序列化的的理解,那么序列化的目的就是未来将某个被序列化的对象还原到程序,简单说就是序列化的目的就是反序列化,这个我不知道总结的对不对,我感觉应该是吧,呵呵.。。。。。。。。
看完上面的定义是不是感觉特别奇怪,怎么定义跟这个名字一点也都不搭配。我也觉得挺有意思的不过人家java中还就是这样定义的。
3. 而将一个对象通过序列化保存到永久存储设备上称为对象的持久化,这个不难理解,哈哈。
4. 一个对象要想能够实现序列化,必须实现Serializable这个接口或者实Externalizable接口,不过我们通常使用第一个Serializable就可以了,而且第二个Externalizable接口也是继承Serializable接口的。。。。
可是我们在API文档中查看这个接口的时候,这个接口却没有发现任何的属性和方法,这个是不是很奇怪呢?所以如果记忆好的话,很多人会联想到前段时间学的java注解Annotation,是不是感觉特别像,@Override 不也是这样吗?可是这里的Serializable称为标记性的接口(Marker Interface),所以java会认为当一个类实现了这个接口,就表示这个类的对象是可以序列化的。
5. 反序列化:有序列化的存在,当然还有一个和它对应的过程称为反序列化,简单的说就是将序列化过程存储下来的对象信息加载回来并可以生成对象,这叫做反序列化。
6. 当一个实现了Serializable类的对象中包含或引用了其他的对象,或者说这个被引用的对象中又包含了其他的对象,这种嵌套的关系,此时你如果试图去序列化这个嵌套关系中最顶层的那个对象的时候,所有的其他的对象都会被定位循环的序列化,同样在反序列化中,所有的对象也会被正确的恢复。
7. 当一个对象被序列化的时候,只会保存非静态的成员变量,不能保存任何静态的成员变量,也不能保存任何实例方法,这里也包括静态实例方法,因为我们知道实例化过程就是保存可以序列化类的对象以及对象的状态和信息,是将堆内存中的对象进行序列化的,而静态的变量是属于类,并不是属于的某个对象,它存在于方法块中,所以静态的属性是不会被序列化的。
8. 如果一个对象的成员变量是一个对象的话,那么这个成员对象也会被序列化的保存起来。
9. 如果一个可序列化的对象中包含不可以序列化的对象的时候,那么整个序列化操作都将失败,并且会抛出NotSerializableException,不过我们可以将这个不可序列化的成员对象定义为transient,那么对象还可以继续序列化。
10. 假如我们在序列化某个对象的时候,如果我们不想把它的某个非静态的属性序列化的话,我们可以在他的声明类型前面加上transient,这样就可以不序列化这个属性了,那么这个属性依然是存在于堆内存或内存中。‘
11. 倘若我们有一个被序列化完的对象,我们后继又去修改了这个对象对应类的信息,如将某个属性的修饰从default类型变成 private类型,或则添加一个属性,然后编译这个类,我们发现再去进行反序列的时候,会发生如下这样的错误,因为在序列化和反序列化这一块,当我们反序列化一个对象过程的时候,会有这样一个机制在运行,序列化运行时,使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致InvalidClassException
,所以序列化时的UID和序列化后的UID发生变化,不匹配,才报错的。
Exception in thread "main"java.io.InvalidClassException:com.serializable2.PersonPerson; local class incompatible: stream classdescserialVersionUID = 7975301502089024376, local class serialVersionUID =4522589182178678017
那如果我们并不希望因为这样的一些后继修改导致无法反序列化,我们就可以在该对象的类中添加一个特有的long类型的常量用来固定这个UID,这样后续不管如何去修改类的信息,反序列化时都可以正常进行。
public static final longserialVersionUID = 142L
修饰符可以任意。
12. 序列化和反序列化的几个主要操作类:
A. 因为序列化是要将某个对象保存到存储设备上,实现对象持久性,所以我们当然要给它创建一个目的地,所以要使用FileOutputStream,当然要使用它的带参数的构造方法,同理反序列化当然是从某个文件读取并还原某个对象。
序列化: ObjectOutputStream(OutputStreamout
)
反序列化:ObjectInputStream(IntputStream in
)
我们来看一段序列化的代码:
//因为是要将对象进行序列化,所以当然首先要创建一个测试类用来做测试,这里我们//创建PersonPerson 这样一个类,下面的内容
//就不多讲了
package com.serializable2;
import java.io.Serializable;
//首先,一个类如果要使它可以被序列化,就必须继承Serliaziable接口
public class PersonPerson implements Serializable
{
private int age;
private String name;
private double height;
public PersonPerson(int age , String name , double height)
{
this.name = name ;
this.age = age ;
this.height = height ;
}
@Override
public String toString()
{
return this.name+","+ this.age+","+this.height;
}
}
//这个类是个用来操作序列化和反序列化
package com.serializable2;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectStreamDemo
{
public static void main(String[] args) throws Exception
{ //然后在Mail方法中调用序列化和反序列操作方法
// writeObj();
readObj();
}
public static void writeObj() throws Exception
{
//定义一个文件字节流输出对象,目的文件问Person.object,并将参数传递
//给ObjectOutputStream对象
FileOutputStream fos = new FileOutputStream("Person.object");
ObjectOutputStream oos = new ObjectOutputStream(fos);
// oos这个对象的writeObject(Objectobj)要接收一个Object类型的对象,所以这//里将new一个//PersonPerson对象传递进去
oos.writeObject(new PersonPerson(24, " Ansen" , 176.8));
oos.close();
}
public static void readObj() throws Exception
{
//定义一个文件字节流输入对象,源文件问Person.object,并将参数传递
//给ObjectInputStream对象
FileInputStream fis = new FileInputStream("Person.object");
ObjectInputStream ois =new ObjectInputStream(fis);
//它的readObject()方法返回一个Object对象,当然这里我们可知它是返回一个//PersonPerson对象,所以我们就直接将其强制转
//换为PersonPerson对象了。
PersonPerson p2=(PersonPerson) ois.readObject();
ois.close();
System.out.println(p2.toString());
}
}
13. 当我们在一个带序列化/反序列化的类中实现了以下private方法的时候(方法声明要与下面的完全保持一致),那么就允许我们以更加底层,更加细粒度,更加DIY的方式控制序列化和反序列化了。
private void writeObject(java.io.ObjectOutputStreamout)
private void readObject(java.io.ObjectInputStream in)
我们来看一个代码吧。。。。。。。
package com.serializable;
importjava.io.FileInputStream;
importjava.io.FileOutputStream;
import java.io.IOException;
importjava.io.ObjectInputStream;
importjava.io.ObjectOutputStream;
import java.io.Serializable;
public classSerializableDemo
{
public static void main(String[] args) throws Exception
{
Person p1 = new Person(24,"张华X1",1.76);
Person p2 = new Person(23,"张华X2",1.75);
Person p3 = new Person(18,"张华X3",1.68);
FileOutputStream fos = newFileOutputStream("Person.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(p1);
oos.writeObject(p2);
oos.writeObject(p3);
oos.close();
System.out.println("================");
FileInputStream fis =new FileInputStream("Person.txt");
ObjectInputStream ois= new ObjectInputStream(fis);
Person p = null ;
for(int i = 0 ; i < 3; i++)
{
p = (Person) ois.readObject();
System.out.println(p.getName()+","+p.getAge()+","+p.getHeight());
}
ois.close();
}
}
class Person implementsSerializable
{
private int age;
private String name;
private double height;
public Person(int age ,String name , double height)
{
this.age = age;
this.name =name;
this.height = height;
}
public int getAge()
{
return age;
}
public String getName()
{
return name;
}
public double getHeight()
{
return height;
}
private void writeObject(java.io.ObjectOutputStream out) throwsException
{
//这里指定的只将age和name写入到序列化中,并没有将另外一个属性写
//入到序列化文件中
out.writeInt(age);
out.writeUTF(name);
System.out.println("writeObject");
}
private voidreadObject(java.io.ObjectInputStream in) throws Exception
{
//因为上面的方法只指定了将age和name保存到文件中,那么这里也只能设定//去读age和name了,而且顺序要一直,
age = in.readInt();
name = in.readUTF();
System.out.println("readObject");
}
}
所以以上最终结果是:
wriwriteObject
writeObject
writeObject
================
readObject
张华X1,24,0.0
readObject
张华X2,23,0.0
readObject
张华见,18,0.0
所以我们看到的结果是并没有height这个变量的值,看到的只是默认值0.0