死磕Java之序列化与反序列化
当创建对象时,它就一直存在,但是在程序终止之后,无论如何它不会存在。如果现在的需求是当程序终止后,需要将信息保存起来,这就需要Java序列化了。
01
序列化的引入
Java序列化的引入主要基于两方面的考虑:
1.为了支持Java的RMI(远程过程调用),它使存活于其他计算机上的对象使用起来就像存在于本机计算机一样。
2.对于Java Beans来说,对象的序列化也是必要的。
02
序列化的定义
Java的序列化定义是将对象性转换为字节序列。实现Java序列化的主要方法有两种,一种是实现Serializable接口,还有一种是使用Externalizable接口。
与Java序列化相反的过程称之为Java反序列化,主要将字节序列转换为对象的过程。
03
序列化与反序列化的过程
要序列化一个对象,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。这时,只需调用writeObject()即可将对象序列化,将其发送给OutputStream(对象化序列是基于字节的,因而使用InputStream和OutputStream继承层次结构)。要反向进行该过程(即将一个序列还原为一个对象),需要将一个InputStream封装在ObjectInputStream内部,然后调用readObject()方法。
04
Serializable接口实现
我们这里定义了一个Account类,Account类实现Serializable接口,下面是Java序列化过程如下:
public class Account implements Serializable { private String username; private double balance; private String password; public Account() { } public Account(String username, double balance, String password) { this.username = username; this.balance = balance; this.password = password; } public String getUsername() { return username; } public double getBalance() { return balance; } public String getPassword() { return password; } public void setUsername(String username) { this.username = username; } public void setBalance(double balance) { this.balance = balance; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "Account{" + "username='" + username + '\'' + ", balance=" + balance + ", password='" + password + '\'' + '}'; } public static void main(String[] args) throws IOException, ClassNotFoundException { Account account=new Account("Bob",343.44,"sdfdas"); //程序猿技术 ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("e:\\object.dat")); out.writeObject(account); }
Account的实例对象序列化到E盘下的object二进制文件中。反序列的过程代码如下:
public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectInputStream in=new ObjectInputStream(new FileInputStream("e:\\object.dat")); Account account=(Account)in.readObject(); System.out.println(account.getUsername()); }
运行结果如下:
不知道你是否注意到,对于反序列化是存在不安全的!如果从文件读取出来的数据反序列化成对象后,文件内含有恶意数据可能危害Java虚拟机。
在上面的代码中,实际生活中密码可能不需要序列化到文件中,每一个人都可以读取文件来获取对象,这样是非常不安全的。因而,如果我们可以选择序列化的字段,这样的序列化方式是非常可去的。下面我们将介绍这种序列化方式。
05
Externalizable接口实现
不同于Serializable接口,Externalizable接口提供了readExternal()方法和writeExternal()方法来可控的序列化对象的字段。上一节中Account类的序列化实现:
public class Account implements Externalizable { private String username; private double balance; private String password;
public Account() {}
public Account(String username, double balance, String password) { this.username = username; this.balance = balance; this.password = password; } public String getUsername() { return username; } public double getBalance() { return balance; } public String getPassword() { return password; } public void setUsername(String username) { this.username = username; } public void setBalance(double balance) { this.balance = balance; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "Account{" + "username='" + username + '\'' + ", balance=" + balance + ", password='" + password + '\'' + '}'; } public static void main(String[] args) throws IOException, ClassNotFoundException { Account account=new Account("Bob",343.44,"sdfdas"); //程序猿技术 ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("e:\\object.dat")); out.writeObject(account); } public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(username); out.writeDouble(balance); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { in.readUTF(); in.readDouble(); } }
需要注意的是,两个方法的字段顺序要一致。
现在测试password字段是否序列化,反序列化的过程如下:
public static void main(String[] args) throws IOException, ClassNotFoundException { //反序列化过程 ObjectInputStream in=new ObjectInputStream(new FileInputStream("e:\\object.dat")); Account account=(Account)in.readObject(); System.out.println("密码:"+account.getPassword()); }
运行结果如下:
从结果可以看出,密码并没有被序列化。
06
transient关键字
可控的序列化还有一种方案,就是在不需要序列化的字段前,使用transient关键字进行修饰,表示将这个字段关闭序列化。代码如下:
public class Account implements Serializable { private String username; private double balance; private transient String password; public Account() { } public Account(String username, double balance, String password) { this.username = username; this.balance = balance; this.password = password; } public String getUsername() { return username; } public double getBalance() { return balance; } public String getPassword() { return password; } public void setUsername(String username) { this.username = username; } public void setBalance(double balance) { this.balance = balance; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "Account{" + "username='" + username + '\'' + ", balance=" + balance + ", password='" + password + '\'' + '}'; } public static void main(String[] args) throws IOException, ClassNotFoundException { Account account=new Account("Bob",343.44,"sdfdas"); //程序猿技术 ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("e:\\object.dat")); out.writeObject(account); //反序列化过程 ObjectInputStream in=new ObjectInputStream(new FileInputStream("e:\\object.dat")); Account account1=(Account)in.readObject(); System.out.println("密码:"+account1.getPassword()); } }
运行的结果如下:
07
序列化的意义
利用序列化可以实现轻量级的持久化,这样可以让一个对象的生命周期并不取决于程序是否在进行;它可以存在于程序调用之间;通过将一个序列化对象写入磁盘,然后重新调用程序是恢复该对象,就能实现持久化。
点击上方二维码,关注我们
15