Java基础学习总结——Java对象的序列化和反序列化
一、序列化和反序列化的概念
把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。
在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
二、JDK类库中的序列化API
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。
对象序列化包括如下步骤:
1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
2) 通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤如下:
1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
2) 通过对象输入流的readObject()方法读取对象。
三、对象序列化和反序列范例
package com.yjj.serialize.object;
import java.io.Serializable;
/**
* @Description:
* @Author: yinjunjie
* @CreateDate: 2018/8/31 21:08
* @Version: 1.0
*/
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private Integer age;
private String name;
public Person() {
}
public Person(Integer age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
序列化
package com.yjj.serialize.serialize;
import com.yjj.serialize.object.Person;
import java.io.*;
/**
* @Description:
* @Author: yinjunjie
* @CreateDate: 2018/8/31 21:06
* @Version: 1.0
*/
public class SerializeTest {
public static void main(String[] args) {
Person person = new Person(12, "殷俊杰");
try {
serialize(person);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void serialize(Object object) throws IOException {
Class clazz = object.getClass();
System.out.println(clazz.getName());
File file = new File("E:/a.txt");
FileOutputStream fileOutputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file);
objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
System.out.printf("序列化对象%s成功",clazz.getName());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (objectOutputStream != null) {
objectOutputStream.close();
}
if (fileOutputStream != null) {
fileOutputStream.close();
}
}
}
}
输出如下
com.yjj.serialize.object.Person
序列化对象com.yjj.serialize.object.Person成功
Process finished with exit code 0
反序列化
package com.yjj.serialize.deserialize;
import com.yjj.serialize.object.Person;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* @Description:
* @Author: yinjunjie
* @CreateDate: 2018/8/31 21:45
* @Version: 1.0
*/
public class DeserializeTest {
public static void main(String[] args) {
File file=new File("E:/a.txt");
Object object=deserialize(file);
Person person= (Person) object;
System.out.println(person);
}
public static Object deserialize(File file){
try {
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(file));
Object object=objectInputStream.readObject();
return object;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
输出如下
Person{age=12, name='殷俊杰'}
serialVersionUID的作用
在序列化时显示指定serialVersionUID作为版本号,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量,如果不显示指定,jvm会隐式给我们随机生成一个serialVersionUID,这个serialVersionUID是根据对象的信息生成的,如果我们对序列化对象有所改动的话,serialVersionUID也会随之改动,再反序列化就会因serialVersionUID不一致而报错、
示例:
package com.yjj.serialize.object;
import java.io.Serializable;
/**
* @Description:
* @Author: yinjunjie
* @CreateDate: 2018/8/31 21:36
* @Version: 1.0
*/
public class User implements Serializable{
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
序列化和反序列化跟上面一样
package com.yjj.serialize.serialize;
import com.yjj.serialize.object.User;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* @Description:
* @Author: yinjunjie
* @CreateDate: 2018/8/31 21:06
* @Version: 1.0
*/
public class SerializeTest2 {
public static void main(String[] args) {
User user = new User("殷俊杰",16);
try {
serialize(user);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void serialize(Object object) throws IOException {
Class clazz = object.getClass();
System.out.println(clazz.getName());
File file = new File("E:/b.txt");
FileOutputStream fileOutputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file);
objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
System.out.printf("序列化对象%s成功",clazz.getName());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (objectOutputStream != null) {
objectOutputStream.close();
}
if (fileOutputStream != null) {
fileOutputStream.close();
}
}
}
}
反序列化
package com.yjj.serialize.deserialize;
import com.yjj.serialize.object.User;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* @Description:
* @Author: yinjunjie
* @CreateDate: 2018/8/31 21:45
* @Version: 1.0
*/
public class DeserializeTest2 {
public static void main(String[] args) {
File file=new File("E:/b.txt");
Object object=deserialize(file);
User user= (User) object;
System.out.println(user);
}
public static Object deserialize(File file){
try {
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(file));
Object object=objectInputStream.readObject();
return object;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
此时是正常且成功的,但是,我们如果现在在User对象新增一个属性
package com.yjj.serialize.object;
import java.io.Serializable;
/**
* @Description:
* @Author: yinjunjie
* @CreateDate: 2018/8/31 21:36
* @Version: 1.0
*/
public class User implements Serializable{
private String name;
private int age;
private String gender;
public User(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
'}';
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
上面我说,serialVersionUID是根据对象信息生成的,现在对象多了一个属性,信息已经改变,再次生成的serialVersionUID 已经和刚才的不一样了,这时我们再进行反序列化会报如下错误
D:\software\jdk\bin\java -javaagent:D:\software\idea\lib\idea_rt.jar=10101:D:\software\idea\bin -Dfile.encoding=UTF-8 -classpath D:\software\jdk\jre\lib\charsets.jar;D:\software\jdk\jre\lib\deploy.jar;D:\software\jdk\jre\lib\ext\access-bridge-64.jar;D:\software\jdk\jre\lib\ext\cldrdata.jar;D:\software\jdk\jre\lib\ext\dnsns.jar;D:\software\jdk\jre\lib\ext\jaccess.jar;D:\software\jdk\jre\lib\ext\jfxrt.jar;D:\software\jdk\jre\lib\ext\localedata.jar;D:\software\jdk\jre\lib\ext\nashorn.jar;D:\software\jdk\jre\lib\ext\sunec.jar;D:\software\jdk\jre\lib\ext\sunjce_provider.jar;D:\software\jdk\jre\lib\ext\sunmscapi.jar;D:\software\jdk\jre\lib\ext\sunpkcs11.jar;D:\software\jdk\jre\lib\ext\zipfs.jar;D:\software\jdk\jre\lib\javaws.jar;D:\software\jdk\jre\lib\jce.jar;D:\software\jdk\jre\lib\jfr.jar;D:\software\jdk\jre\lib\jfxswt.jar;D:\software\jdk\jre\lib\jsse.jar;D:\software\jdk\jre\lib\management-agent.jar;D:\software\jdk\jre\lib\plugin.jar;D:\software\jdk\jre\lib\resources.jar;D:\software\jdk\jre\lib\rt.jar;D:\workspace\demo-all\serialize\target\classes com.yjj.serialize.deserialize.DeserializeTest2
null
java.io.InvalidClassException: com.yjj.serialize.object.User; local class incompatible: stream classdesc serialVersionUID = 6219457185932359615, local class serialVersionUID = 7483460167538010623
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1880)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1746)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2037)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428)
at com.yjj.serialize.deserialize.DeserializeTest2.deserialize(DeserializeTest2.java:27)
at com.yjj.serialize.deserialize.DeserializeTest2.main(DeserializeTest2.java:19)
Process finished with exit code 0
我们指定serialVersionUID后就可以随意修改了
网络编程发送对象示例
总结
- 如果对象需要被写出到文件上,那么对象所属的类必须要实现Serializable接口。 Serializable接口没有任何的方法,是一个标识接口而已。
- 对象的反序列化创建对象的时候并不会调用到构造方法的、(这点文中没有说到,想要验证的同学在构造方法后面加一句System.out.println("构造方法执行吗?");,实际上构造方法是不执行的,自然这句话也没有输出了)
- serialVersionUID 是用于记录class文件的版本信息的,serialVersionUID这个数字是通过一个类的类名、成员、包名、工程名算出的一个数字。
- 使用ObjectInputStream反序列化的时候,ObjeectInputStream会先读取文件中的serialVersionUID,然后与本地的class文件的serialVersionUID
进行对比,如果这两个id不一致,反序列则失败。 - 如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID,如果一类已经指定的serialVersionUID,然后
在序列化与反序列化的时候,jvm都不会再自己算这个 class的serialVersionUID了。 - 如果一个对象某个数据不想被序列化到硬盘上,可以使用关键字transient修饰。
- 如果一个类维护了另外一个类的引用,则另外一个类也需要实现Serializable接口。
网友评论