好记性不如烂笔头!
序列化是一种对象持久化的手段,普遍应用于网络传输和远程方法调用(RMI)等场景中。
1.概念
- 序列化:把对象转换为字节序列的过程称为对象的序列化。
- 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
2.作用
- 实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里)
- 利用序列化实现远程通信,即在网络上传送对象的字节序列。
3.应用场景
- Java远程方法调用(RMI),即允许一个Java虚拟机上运行的Java程序调用其他虚拟机运行内存中对象的方法,即使这些虚拟机运行于物理隔离的不同主机上。
- 分布式系统中不同服务器间共享的JavaBean对象都需要先序列化为二进制数据,再在网络中传输。
- Session钝化机制:Web容器将一些session先序列化写入本地磁盘,在需要使用时再将对象从磁盘还原到内存中去,以减轻服务器的内存负担。
4.实现序列化的方式
- 实现Serializable接口;
import java.io.*; import java.math.BigDecimal; public class User implements Serializable { //private static final long serialVersionUID = -8536979444227018818L; private static String SEX = "男"; private String name; private String age; transient BigDecimal money; /*private Boolean haveGirlFirend; public Boolean getHaveGirlFirend() { return haveGirlFirend; } public void setHaveGirlFirend(Boolean haveGirlFirend) { this.haveGirlFirend = haveGirlFirend; }*/ public static String getSEX() { return SEX; } public static void setSEX(String SEX) { User.SEX = SEX; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public BigDecimal getMoney() { return money; } public void setMoney(BigDecimal money) { this.money = money; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age='" + age + '\'' + ", money=" + money + '}'; } }
注意:代码中注释了serialVersionUID 、haveGirlFirend。便于后续测试反序列可能出现的问题。
import java.io.*; import java.math.BigDecimal; /** * 序列化与反序列化测试 * @author Administrator */ public class UserTest { public static void main(String[] args) throws IOException, ClassNotFoundException { serializableUser(); User user = deserializeUser(); System.out.println(user.toString()); } /** * 序列化 * @throws IOException */ private static void serializableUser() throws IOException { User user = new User(); user.setName("zhangsan"); user.setAge("22"); user.setMoney(new BigDecimal("501023")); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:/user.txt"))); oos.writeObject(user); System.out.println("User 对象序列化成功!"); oos.close(); } /** * 反序列化 * @return * @throws IOException * @throws ClassNotFoundException */ private static User deserializeUser() throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/user.txt"))); User flyPig = (User)ois.readObject(); System.out.println("User 对象反序列化成功!"); ois.close(); return flyPig; } }
执行结果:并且先序列化User,然后去修改属性AGE你会发现:
声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据。
在没有serialVersionUID 的情况下序列化User后,放开haveGirlFirend属性的注释再去反序列化的话,会出现异常。由此说明:serialVersionUID保证版本号的一致性。若不显式指定该常量值,Java编译器会自动为其生成一个默认值,该默认值会随着类代码的变动而变动。故当修改类代码时,之前已经序列化的对象在进行反序列化时即会因为版本号的不一致而发生异常。
- 实现Serializable接口并定义writeObject、readObject方法进行序列化与反序列化;
- 实现Externalizable接口并实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化:
ObjectOutputStream调用User对象的writeExternal(ObjectOutput out))的方法进行序列化。 ObjectInputStream会调用User对象的readExternal(ObjectInput in)的方法进行反序列化。
其中,ArrayList类即采用以上方式来实现对序列化的控制,主要是为了保证仅对动态数组中的非null元素进行序列化。
扩展:若一个对象的属性引用其他对象,则序列化该对象时引用对象也会同时被序列化,这即是序列化能够实现深拷贝的本质。鄙人才疏学浅,若有不当之处,望各位雅正。