ToB企服应用市场:ToB评测及商务社交产业平台

标题: 面试题:Java序列化与反序列化 [打印本页]

作者: 风雨同行    时间: 2022-9-16 17:20
标题: 面试题:Java序列化与反序列化
目录

作者:小牛呼噜噜 | https://xiaoniuhululu.com
计算机内功、JAVA底层、面试相关资料等更多精彩文章在公众号「小牛呼噜噜 」
序列化和反序列化的概念

当我们在Java中创建对象的时候,对象会一直存在,直到程序终止时。但有时候可能存在一种"持久化"场景:我们需要让对象能够在程序不运行的情况下,仍能存在并保存其信息。当程序再次运行时 还可以通过该对象的保存下来的信息 来重建该对象。序列化和反序列化 就应运而生了,序列化机制可以使对象可以脱离程序的运行而独立存在。
应用场景?


序列化实现的方式

如果使用Jdk自带的序列化方式实现对象序列化的话,那么这个类应该实现Serializable接口或者Externalizable接口
继承Serializable接口,普通序列化

首先我们定义一个对象类User
  1. public class User implements Serializable {
  2.     //序列化ID
  3.     private static final long serialVersionUID = 1L;
  4.     private int age;
  5.     private String name;
  6.     public User(int age, String name) {
  7.         this.age = age;
  8.         this.name = name;
  9.     }
  10.     public static long getSerialVersionUID() {
  11.         return serialVersionUID;
  12.     }
  13.     public int getAge() {
  14.         return age;
  15.     }
  16.     public void setAge(int age) {
  17.         this.age = age;
  18.     }
  19.     public String getName() {
  20.         return name;
  21.     }
  22.     public void setName(String name) {
  23.         this.name = name;
  24.     }
  25. }
复制代码
然后我们编写一下测试类:
  1. public class serTest {
  2.     public static void main(String[] args) throws Exception, IOException {
  3.         SerializeUser();
  4.         DeSerializeUser();
  5.     }
  6.     /**
  7.      * 序列化方法
  8.      * @throws IOException
  9.      */
  10.     private static void SerializeUser() throws  IOException {
  11.         User user = new User(11, "小张");
  12.         //序列化对象到指定的文件中
  13.         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\jun\\Desktop\\example"));
  14.         oos.writeObject(user);
  15.         oos.close();
  16.         System.out.println("序列化对象成功");
  17.     }
  18.     /**
  19.      * 反序列化方法
  20.      * @throws IOException
  21.      * @throws ClassNotFoundException
  22.      */
  23.     private static void DeSerializeUser() throws  IOException, ClassNotFoundException {
  24.         //读取指定的文件
  25.         File file = new File("C:\\Users\\jun\\Desktop\\example");
  26.         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
  27.         User newUser = (User)ois.readObject();
  28.         System.out.println("反序列化对象成功:"+ newUser.getName()+ ","+newUser.getAge());
  29.     }
  30. }
复制代码
结果:
序列化对象成功
反序列化对象成功:小张,11
一个对象想要被序列化,那么它的类就要继承Serializable接口或者它的子接口
继承Serializable接口类的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。如果不想序列化的字段可以使用transient关键字修饰
  1. private int age;
  2. private String name;
  3. private transient password;//属性:密码,不想被序列化
复制代码
我们需要注意的是:使用transient关键字阻止序列化虽然简单方便,但被它修饰的属性被完全隔离在序列化机制之外,这必然会导致了在反序列化时无法获取该属性的值。
其实我们完全可以在通过在需要序列化的对象的Java类里加入writeObject()方法与readObject()方法来控制如何序列化各属性,某些属性是否被序列化
如果User有一个属性是引用类型的呢?比如User其中有一个属性是类Person:
  1. private Person person;
复制代码
那如果要想User可以序列化,那Person类也必须得继承Serializable接口,不然程序会报错
另外大家应该注意到serialVersionUID了吧,在日常开发的过程中,经常遇到,暂且放放,我们后文再详细讲解
继承Externalizable接口,强制自定义序列化

对于Externalizable接口,我们需要知道以下几点:
首先我们定义一个对象类ExUser
  1. public class ExUser implements Externalizable {
  2.     private int age;
  3.     private String name;
  4.     //注意,必须加上pulic 无参构造器
  5.     public ExUser() {
  6.     }
  7.     public int getAge() {
  8.         return age;
  9.     }
  10.     public void setAge(int age) {
  11.         this.age = age;
  12.     }
  13.     public String getName() {
  14.         return name;
  15.     }
  16.     public void setName(String name) {
  17.         this.name = name;
  18.     }
  19.    
  20.     @Override
  21.     public void writeExternal(ObjectOutput out) throws IOException {
  22.         out.writeObject(name);
  23.         out.writeInt(age);
  24.     }
  25.     @Override
  26.     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
  27.         this.name = (String)in.readObject();
  28.         this.age = in.readInt();
  29.     }
  30. }
复制代码
我们接着编写测试类:
  1. public class serTest2 {
  2.     public static void main(String[] args) throws Exception, IOException {
  3.         SerializeUser();
  4.         DeSerializeUser();
  5.     }
  6.     /**
  7.      * 序列化方法
  8.      * @throws IOException
  9.      */
  10.     private static void SerializeUser() throws  IOException {
  11.         ExUser user = new ExUser();
  12.         user.setAge(10);
  13.         user.setName("小王");
  14.         //序列化对象到指定的文件中
  15.         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\jun\\Desktop\\example"));
  16.         oos.writeObject(user);
  17.         oos.close();
  18.         System.out.println("序列化对象成功");
  19.     }
  20.     /**
  21.      * 反序列化方法
  22.      * @throws IOException
  23.      * @throws ClassNotFoundException
  24.      */
  25.     private static void DeSerializeUser() throws  IOException, ClassNotFoundException {
  26.         File file = new File("C:\\Users\\jun\\Desktop\\example");
  27.         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
  28.         ExUser newUser = (ExUser)ois.readObject();
  29.         System.out.println("反序列化对象成功:"+ newUser.getName()+ ","+newUser.getAge());
  30.     }
  31. }
复制代码
结果:
序列化对象成功
反序列化对象成功:小王,10
因为序列化和反序列化方法需要自己实现,因此可以指定序列化哪些属性,transient关键字在这里是无效的。
对Externalizable对象反序列化时,会先调用类的无参构造方法,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor异常,因此Externalizable对象必须有默认构造函数,而且必需是public的。
serialVersionUID的作用

如果反序列化使用的serialVersionUID与序列化时使用的serialVersionUID不一致,会报InvalidCalssException异常。这样就保证了项目迭代升级前后的兼容性
serialVersionUID是序列化前后的唯一标识符,只要版本号serialVersionUID相同,即使更改了序列化属性,对象也可以正确被反序列化回来。
默认如果没有人为显式定义过serialVersionUID,那编译器会为它自动声明一个!
serialVersionUID有两种显式的生成方式:
private static final long serialVersionUID = xxxxL;
静态变量不会被序列化

凡是被static修饰的字段是不会被序列化的,我们来看一个例子:
  1. //实体类
  2. public class Student implements Serializable {
  3.     private String name;
  4.     public static Integer age;//静态变量
  5.     public String getName() {
  6.         return name;
  7.     }
  8.     public void setName(String name) {
  9.         this.name = name;
  10.     }
  11.     public static Integer getAge() {
  12.         return age;
  13.     }
  14.     public static void setAge(Integer age) {
  15.         Student.age = age;
  16.     }
  17. }
  18. //测试类
  19. public class shallowCopyTest {
  20.     public static void main(String[] args) throws Exception {
  21.         Student student1 = new Student();
  22.         student1.age = 11;
  23.         //序列化,将数据写入指定的文件中
  24.         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\student1"));
  25.         oos.writeObject(student1);
  26.         oos.close();
  27.         Student student2 = new Student();
  28.         student2.age = 21;
  29.         //序列化,将数据写入指定的文件中
  30.         ObjectOutputStream oos2 = new ObjectOutputStream(new FileOutputStream("D:\\student2"));
  31.         oos2.writeObject(student1);
  32.         oos2.close();
  33.         //读取指定的文件
  34.         File file = new File("D:\\student1");
  35.         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
  36.         Student student1_new = (Student)ois.readObject();
  37.         System.out.println("反序列化对象,student1.age="+ student1_new.getAge());
  38.         //读取指定的文件
  39.         File file2 = new File("D:\\student1");
  40.         ObjectInputStream ois2 = new ObjectInputStream(new FileInputStream(file2));
  41.         Student student2_new = (Student)ois2.readObject();
  42.         System.out.println("反序列化对象,student2.age="+ student2_new.getAge());
  43.     }
  44. }
复制代码
结果:
反序列化对象,student1.age=21
反序列化对象,student2.age=21
为啥结果都是21?
我们知道对象的序列化是操作的堆内存中的数据,而静态的变量又称作类变量,其数据存放在方法区里,类一加载,就初始化了。
又因为静态变量age没有被序列化,根本就没写入文件流中,所以我们打印的值其实一直都是当前Student类的静态变量age的值,而静态变量又是所有的对象共享的一个变量,所以就都是21
使用序列化实现深拷贝

我们再来看一个例子:
  1. //实体类 继承Cloneable
  2. public class Person implements Serializable{
  3.     public String name;//姓名
  4.     public int height;//身高
  5.     public StringBuilder something;
  6. ...//省略 getter setter
  7.     public Object deepClone() throws Exception{
  8.         // 序列化
  9.         ByteArrayOutputStream bos = new ByteArrayOutputStream();
  10.         ObjectOutputStream oos = new ObjectOutputStream(bos);
  11.    
  12.         oos.writeObject(this);
  13.    
  14.         // 反序列化
  15.         ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
  16.         ObjectInputStream ois = new ObjectInputStream(bis);
  17.    
  18.         return ois.readObject();
  19.     }
  20. }
  21. //测试类,这边类名笔者就不换了,在之前的基础上改改
  22. public class shallowCopyTest {
  23.     public static void main(String[] args) throws Exception {
  24.         Person p1 = new Person("小张", 180, new StringBuilder("今天天气很好"));
  25.         Person p2 = (Person)p1.deepClone();
  26.         System.out.println("对象是否相等:"+ (p1 == p2));
  27.         System.out.println("p1 属性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
  28.         System.out.println("p2 属性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());
  29.         // change
  30.         p1.setName("小王");
  31.         p1.setHeight(200);
  32.         p1.getSomething().append(",适合出去玩");
  33.         System.out.println("...after p1 change....");
  34.         System.out.println("p1 属性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
  35.         System.out.println("p2 属性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());
  36.     }
  37. }
复制代码
结果:
对象是否相等:false
p1 属性值=小张,180,今天天气很好
p2 属性值=小张,180,今天天气很好
...after p1 change....
p1 属性值=小王,200,今天天气很好,适合出去玩
p2 属性值=小张,180,今天天气很好
详情见:https://mp.weixin.qq.com/s/M4--Btn24NIggq8UBdWvAw
常见序列化协议对比

除了JDK 自带的序列化方式,还有一些其他常见的序列化协议:
采用哪种序列化方式,我们一般需要考虑序列化之后的数据大小,序列化的耗时,是否支持跨平台、语言,或者公司团队的技术积累。这边就不展开讲了,大家感兴趣自行去了解
小结

参考资料:
《On Java 8》
https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html
https://www.zhihu.com/question/26475281/answer/1898221893
本篇文章到这里就结束啦,很感谢你能看到最后,如果觉得文章对你有帮助,别忘记关注我!更多精彩的文章


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4