我是靠谱客的博主 多情荔枝,最近开发中收集的这篇文章主要介绍Java之序列化和反序列化,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

字符流和字节流,他们的操作对象是:字节(二进制),字符 序列化和反序列化的操作主体对象是:对象和二进制

什么是序列化和反序列化?

序列化:将对象转换成二进制
反序列化:从二进制中将二进制转换成对象(类)

为什么需要序列化和反序列化?

因为系统数据间的交互,一定传输的是二进制对象,而我们编写代码的时候不可能使用二进制进行编写,所以我们编写业务代码的时候使用的是类对象,当别人调用我的API的时候调用的是二进制,因此需要先序列化,将对象转换成二进制进行传输,那么在读取到二进制的时候进行反序列化操作就可以将二进制编程我们能够看懂的对象

序列化和反序列化的作用
  • 序列化的作用:在传递和保存对象时.保证对象的完整性和可传递性。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。

  • 反序列化的作用:根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。

    序列化的优点
    
  • 将对象转为字节流存储到硬盘上,当JVM停机的话,字节流还会在硬盘上默默等待,等待下一次JVM的启动,把序列化的对象,通过反序列化为原来的对象,并且序列化的二进制序列能够减少存储空间(永久性保存对象)。

  • 序列化成字节流形式的对象可以进行网络传输(二进制形式),方便了网络传输。

  • 通过序列化可以在进程间传递对象。

    实现序列化的一些方式,如:
    

1、Java对象序列化: ObjectInputStream(序列化),ObjectOutputStream (反序列化)
2、JSON序列化 (目前最常用)
3、XML(早期使用)
4、Protostuff
5、Hession(它基于HTTP协议传输,使用Hessian二进制序列化,对于数据包比较大的情况比较友好。)
6、Dubbo Serialization(阿里dubbo序列化)
7、FST(高性能、序列化速度大概是JDK的4-10倍,大小是JDK大小的1/3左右)
8、自定义协议进行序列化

序列化操作和反序列化操作

首先定义一个Person类,然后提供get和set方法,且要实现Serializable接口

class Person implements Serializable {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
序列化操作

实现序列化必须实现Serializable接口,如果没有实现这个接口就会报错——NotSerializableException;

序列化代码实现

//序列化
    private static void serialization() {
        //创建一个Person对象
        Person person = new Person();
        person.setId(100);
        person.setName("Java");
        String filePath = "D:\io_test\2\person.txt";
        try {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
            //序列化要调用writeObject()方法,然后将对象传进去
            objectOutputStream.writeObject(person);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

执行结果,打开person文档查看结果其内容被序列化成二进制
在这里插入图片描述

反序列化操作
 //反序列化,因为反序列化会把二进制转换成一个对象,所以返回值就使用定义的Person
    private static Person deserialization() {
        //创建一个对象用来接收
        Person person = null;
        //反序列化需要操作的文件地址,也就是我们刚刚序列化的文件
        String filePath = "D:\io_test\2\person.txt";
        try{
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filePath));
            {
                //这里需要将强转成Person类
                person = (Person) objectInputStream.readObject();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return person;
    }

如何验证反序列成功了呢?那就将他的属性打印出来,如果是我们最初定义的值,那么就是成功的
在这里插入图片描述

序列化和反序列化注意问题

注意问题一:版本号UID,也就是当一个文件被序列化之后,然后再类里面添加其他属性的时候,此时反序列化的结果是读取不到新添加的属性的值

在这里插入图片描述
那么如何解决这个自定义类不一致的问题呢?就需要使用UID,可以看到,如果不定义一个UID那么Java内部会自定义的设置两个不同的UID
在这里插入图片描述
在Person里面定义一个serialVersionUID
在这里插入图片描述
测试代码:
测试步骤:
1.进行序列化操作
2.添加新的属性name
3.进行反序列化查看结果(结果是对应的属性,如String就会返回null)
序列化部分

import java.io.*;

/**
 * 版本UID问题
 */
public class IoDemo12 {
    static class  Person implements Serializable {
        public final static long  serialVersionUID = 1L;
         private int id;
        private int age;
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }
    public static void main(String[] args) {
        Person person =  new Person();
        person.setId(111);
        person.setAge(18);
        person.setName("豹子头");
        //需要将二进制存在哪里的路径
        String filePath = "D:\io_test\2\person1";
        try {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
            {
                //进行序列化操作
                objectOutputStream.writeObject(person);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

反序列化部分

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;

public class IoDemo13 {
    public static void main(String[] args) {
        IoDemo12.Person person = null;
        String filePath = "D:\io_test\2\person1";
        try{
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filePath));
            {
                person = (IoDemo12.Person) objectInputStream.readObject();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("person id :"+person.getId());
        System.out.println("person name :"+person.getName());
    }
}

执行结果
在这里插入图片描述

总结:
serialVersionUID可以控制版本了,当出现版本不一致的情况,就能够迅速定位到问题并解决,如果不设置UID(不修改)就会造成,读取数据为(引用类型)null/(int)0的业务逻辑问题(假如时新的版本就可以正常读取到值,可以进行下面的业务逻辑,反之,如果时旧版本的二进制序列化文件,可以进行舍弃)

1.当进行序列化和反序列化时显示的设置此值

2.每次对实体类进行修改之后更新UID的值

注意问题二:临时变量——transient

假如一个类里面有敏感信息,不想让他被序列化,那么就可以让他编程临时变量
在这里插入图片描述

注意问题三:static变量

在这里插入图片描述
为什么count的值已经被序列化了,但是还是能修改它的值呢?

  • 因为static的变量在序列化的时候并没有保存
  • 所有的而属性设置都是来描述对象(实例)——对象属性(可以被序列化)
  • 静态变量是用来描述类的——类属性(不能被序列化)

结果:
当序列化之后再修改的静态属性值并没有被修改,而那些在序列化之后被修改的属性,再被序列化之后就会被覆盖(修改)掉了

问题四:序列化父类问题
  • 如果子类实现了序列化的接口(Serializable) ,而父类没有实现序列化接口,就会出现子类的属性会进行序列化保存,而父类不会进行序列化数据保存

  • 父类实现序列化的接口,所有子类都会正常序列化和反序列化

总结:

序列化和反序列化的注意点:

①序列化时,只对对象的状态进行保存,而不管对象的方法;

②当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;

③当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;

④并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:

安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行RMI传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的;

资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现;

⑤声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据。

⑥序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。为它赋予明确的值。显式地定义serialVersionUID有两种用途:

  • 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;

  • 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

⑦Java有很多基础类已经实现了serializable接口,比如String,Vector等。但是也有一些没有实现serializable接口的;

⑧如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因;

注意:浅拷贝请使用Clone接口的原型模式。

如果想要了解更多,可以点击这里
https://blog.csdn.net/songchuwang1868/article/details/104003193?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161709538816780274114748%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=161709538816780274114748&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_click~default-3-104003193.first_rank_v2_pc_rank_v29&utm_term=%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96&spm=1018.2226.3001.4187

最后

以上就是多情荔枝为你收集整理的Java之序列化和反序列化的全部内容,希望文章能够帮你解决Java之序列化和反序列化所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部