Java 对象序列化
java平台允许在内存中创建可复用的java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即这些对象的生命生命周期不会比JVM生命周期长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。 Java对象序列化就能够实现该功能。使用Java 对象序列化,在保存对象时,会将其转换为字节序列,这些序列可以保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象。
序列化缘由
有时候我们想把一个java对象写入到磁盘文件或传输到网络到其他计算机,这时我们就需要去把这个对象转换成字节流,另一端接收字节流将其恢复成java对象。所以就有了java序列化的概念了。
好处
- 实现了数据的持久化
- 实现 远程通信
序列化ID
序列化ID 提供两种生成策略: 一是固定的1L,一个是随机生成一个不重复的long类型数据(实际上是使用JDK工具生成) 。
Java 的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致,在进行反序列化时,JVM会把传过来的字节流中的serialVersionUID与本地相应实体(类) 的serialVersionUID进行比较,如果相同,则进行反序列化,否则就会出现序列化版本不一致的异常。
如何实现序列化
- 实现Serializable 接口即可
当实现Serializable 接口的实体没有显示定义一个名为serialVersionUID,类型为long变量时,java序列化机制会根据编译的class自动生成一个serialVersionUID做序列化版本比较,这种情况下,只有通一次编译生成的class才会生成相同的serialVersionUID。
实例:
普通对象序列化
public class UserInfo implements Serializable{
private static final long serialVersionUID = 1L;
private String userName;
private int age;
private String address;
public UserInfo(String userName, int age, String address) {
super();
System.out.println("有参数的构造器");
this.userName = userName;
this.age = age;
this.address = address;
}
..... // get、set 方法省略
}
/**
* ObjectStream
*
* @author mingx
*
*/
public class ObjectStream {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = null;
ObjectInputStream oin = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("E:/javaFile/object.txt"));
UserInfo userInfo = new UserInfo("KANNO爷", 25, "幽冥谷");
// 将对象写入输出流
oos.writeObject(userInfo);
// 反序列化操作
oin = new ObjectInputStream(new FileInputStream("E:/javaFile/object.txt"));
UserInfo user = (UserInfo) oin.readObject();
System.out.println("名字为:" + user.getUserName() + "\n 年龄为:" + user.getAge() + "\n地址为:" + user.getAddress());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
oos.close();
}
if (oin != null) {
oin.close();
}
}
}
}
序列化: 上面的程序创建ObjectOutputStream,这个输出流建立在一个文件输出流的基础上,使用writeObject() 方法将一个UserInfo对象写入输出流,运行上面程序生成一个object.txt文件,该文件的内容就是UserInfo对象。
反序列化:创建一个ObjectInputStream,这个输入流是一个处理流,所以必须建立在其他节点流的基础上,调用readObject() 方法读取流中的对象,该方法返回一个Object类型的Java对象,如果程序知道该java对象的类型,将强制转换成其真实类型。
注意:UserInfo类中只定义了一个有参数的构造函数,当我们反序列化读取Java对象时,并没有调用该构造函数,这表明反序列化机制无须通过构造器来初始化Java对象。
如果一个可序列化有多个父类,则该类的所有父类要么是可序列化的,要么有无参数的构造器,否则反序列化时会抛出InvalidClassException异常。
对象引用序列化
如果某个类的属性类型是一个引用类型,那么这个引用类型必须是可序列哈的,否则拥有该类型属性的类是不可序列化的。例如:
public class ObjectReferenceSeriable {
public static void main(String[] args) throws IOException, ClassNotFoundException{
ObjectOutputStream out = null;
ObjectInputStream oin = null;
try {
//序列化操作
out = new ObjectOutputStream(new FileOutputStream("E:\\javaFile\\teacher.txt"));
UserInfo userInfo = new UserInfo("龙幽", 25);
Teacher t1 = new Teacher("净天教", userInfo);
Teacher t2 = new Teacher("蛮族", userInfo);
// 依次将四个对象写入输出流
out.writeObject(t1);
out.writeObject(t2);
out.writeObject(userInfo);
out.writeObject(t2);
//反序列化操作
oin = new ObjectInputStream(new FileInputStream("E:\\javaFile\\teacher.txt"));
// 依次读取输入流的四个对象
Teacher T1 = (Teacher) oin.readObject();
Teacher T2 = (Teacher) oin.readObject();
UserInfo user= (UserInfo) oin.readObject();
Teacher T3 = (Teacher) oin.readObject();
System.out.println("T1的student引用和user是否相同:" + (T1.getStudent() == user));
System.out.println("T2的student引用和user是否相同:" + (T2.getStudent() == user));
System.out.println("T2和T3是否同一个对象:" + (T2==T3));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
finally {
if(out !=null){
out.close();
}
if(oin !=null){
oin.close();
}
}
}
public static class Teacher implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String name;
private UserInfo student;
public Teacher(String name,UserInfo student){
this.name=name;
this.student= student;
}
...... //get、set省略
}
通过上面后面的程序代码比较,可以看出T2和T3是同一个对象,T1的student引用的、T2的student引用的和user引用变量引用的是同一个对象。说明java序列化的机制如下图:
从上图中我们可以看出:当我们多次调用writeObject 输出同一个对象时,程序只有第一次调用writeObject方法才会将该对象转换成字节序列并输出。
但这会照成另外一个问题:当程序序列化一个可变对象时,程序只有第一次使用writeObject()方法输出时才会将该对象转换成字节序列并输出,即使后面该对象的属性已改变,改变的属性值也不会被输出,例如:
public class SerializeMutable {
public static void main(String[] args) throws IOException{
ObjectOutputStream out = null;
ObjectInputStream oin = null;
try {
out = new ObjectOutputStream(new FileOutputStream("E:\\javaFile\\mutable.txt"));
UserInfo user = new UserInfo("pepper",25);
out.writeObject(user);
user.setUserName("Tony");
out.writeObject(user);
// 反序列化
oin = new ObjectInputStream(new FileInputStream("E:\\javaFile\\mutable.txt"));
UserInfo u1 = (UserInfo) oin.readObject();
UserInfo u2 = (UserInfo) oin.readObject();
System.out.println(u1 == u2);
System.out.println(u2.getUserName());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
finally {
if(out !=null){
out.close();
}
if(oin !=null){
oin.close();
}
}
}
}
运行上面程序,程序比较读取的Java对象完全相同,第二次读取的UserInfo对象的userName 属性依然是pepper,说明被改变的userInfo对象并没有被写入。
**静态变量序列化 **
public class StaticSeriable implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
//定义初始值为5
public static int staitcAcitivar = 5;
public static void main(String[] args){
ObjectOutputStream out;
try {
out = new ObjectOutputStream(new FileOutputStream("result.obj"));
out.writeObject(new StaticSeriable());
out.close();
//序列化后修改为10
StaticSeriable.staitcAcitivar = 10;
ObjectInputStream oIn = new ObjectInputStream(new FileInputStream("result.obj"));
StaticSeriable t = (StaticSeriable) oIn.readObject();
oIn.close();
//再读取,通过t.staticActivar打印新值
System.out.println(t.staitcAcitivar);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
上面程序将对象序列化后,修改静态便令的值,再将对象从序列化对象读取出来,打印对象的静态变量值,是10而不是5,这是为什么呢?这是因为序列化时,并不保存静态变量,而是保存对象的状态,静态变量属于类的状态。