序列化和反序列化
什么是序列化和反序列化?
把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。
总之,序列化和反序列化操作的是java对象(Object)
序列化:把java对象转换成二进制码
反序列化:把java对象的二进制码转换回java对象
对象的序列化主要有两种用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。
在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
如何实现序列化和反序列化?
1.让想要可序列化的对象实现空接口Serializable,意思是表示该类的对象可序列化.
2.使用java.io包内的序列化API来实现序列化和反序列化.
可序列化API
java.io.ObjectOutputStream :
public ObjectOutputStream(OutputStream out) : 构造函数,以OutputStream对象作为参数
public final void writeObject(Object obj) : 把对象转换成二进制码并写入到本地文件
java.io.ObjectInputStream :
public ObjectInputStream(InputStream in) : 构造函数,以InputStream对象作为参数
public final Object readObject() : 读取本地文件中保存的二进制码,并转换为Object对象
实例
Person类:
public class Person implements Serializable {
//序列化的版本号,下边将会介绍
private static final long serialVersionUID = 4744551959239088922L;
private int id;
private String name;
private String sex;
private int age;
private String desc;
public Person(int id, String name, String sex, int age, String desc) {
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
this.desc = desc;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + ", desc=" + desc + "]";
}
}
序列化和反序列化测试类:
public class TestObjSerializeAndDeserialize { File File = new File("C:/test/person.txt"); /** * 序列化,把Person对象序列化并存储到本地文件中(C:/test/person.txt) */ @Test public void serializePerson() { Person p = new Person(1,"张三", "男", 18, "废人一个"); try { ObjectOutputStream objOutStream = new ObjectOutputStream(new FileOutputStream(File)); objOutStream.writeObject(p); System.out.println("对象序列化成功"); objOutStream.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 反序列化,读取本地文件的二进制内容,反序列化为Person对象 */ @Test public void deserialize(){ try { ObjectInputStream objInStream = new ObjectInputStream(new FileInputStream(File)); Person person = (Person)objInStream.readObject(); System.out.println(person); objInStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
什么是serialVersionUID?
凡是实现了Serializable接口的类,都会有一个serialVersionUID静态常量
如果没有手动设定该变量,那么IDE会报警告,如下图
点击Add generated serial version ID即可根据类文件自动生成serialVersionUID变量.
为什么要显式地设置serialVersionUID?
1.避免修改对象的类内容导致的serialVersionUID值不一致 :
如果没有显式地声明serialVersionUID静态常量,那么Java编译器会自动给该类生成一个唯一的serialVersionUID,只要类的内容稍有不同,
该类的serialVersionUID值就会完全不同.由此我们可以推断出 :
如果把对象序列化存储到本地后,我们修改了对象的类的内容,那么该类的serialVersionUID也会不同了,
这时我们反序列化该类,调用readObject()方法时,就会报异常:java.io.InvalidClassException,内容为前后serialVersionUID值不匹配
如果我们显式地声明了serialVersionUID静态常量;就可以避免该异常,即使修改类的内容,也可以正常进行反序列化.
2.避免相同类文件用不同java编译器导致的serialVersionUID值不一致 :
类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,
有可能会导致不同的 serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,
强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值
序列化并存储到本地文件后,打开文件的乱码问题 :
正常.序列化后,输出的完全是二进制内容,所以是乱码
f