Home Tags Posts tagged with "序列化,反序列化,Serializalbe"

序列化,反序列化,Serializalbe

0 98

Java序列化的定义(对象—>字节序列)

序列化:把对象转化成可传输的字节序列的过程称为序列化;

反序列化:把字节序列还原成对象的过程称为反序列化;

 

为什么要序列化?(即序列化的目的)

目的:序列化的最终目的是为了对象可以跨平台存储,和进行网络传输;而我们进行跨平台存储和网络传输的方式就是IO,而IO支持的数据格式就是字节数组;

序列化的本质:一种约定的规则(类似于协议)

因为我们单方面的只把对象转成字节数组还不行,因为没有规则的字节数组我们无法将对象的本来面貌还原,所以我们必须在把对象转成字节数组的时候就制定一种规则(序列化),那么我们从IO刘里面读出数据的时候再按照这种规则把对象还原回来(反序列化);

举例:有一座房子(对象),要将房子从A地(A计算机)运到B地(B计算机或设备),序列化就是把房子拆成一块块的砖(序列化,对象—>字节数组),然后留下一张房子原来结构的图纸(序列化规则),反序列化就是我们把房子运输到目的地之后,根据图纸将一块块砖头还原成房子原来面目的过程

补充:字节与字符的区别

字节(Byte):计量单位,表示数据量的多少,是计算机信息技术用于计量存储容量的一种计量单位,通常情况下一字节(Byte)等于八位;

字符(Character):计算机种使用的字母,数字,字和符号,如’A‘,’B‘,12,’&‘等;

什么情况下需要序列化?(应用)

凡是需要进行跨平台存储和网络传输的数据都需要序列化;

本质上存储(写入硬盘等操作)和网络传输都需要经过把一个对象状态保存成一种跨平台识别的字节格式,然后其他的平台才可以通过字节信息解析还原对象信息;

举例:从网络上下载mp3音乐存储到手机上,需要经过把mp3类型的对象状态保存成跨平台识别的字节格式,然后手机可以通过字节解析(反序列化)还原对象信息;

序列化的方式(实现)

序列化只是一种拆装组装对象的规则,那么这种规则肯定也可能有多种多样的(好比有多种协议),常见的序列化方式:

JDK(不支持跨语言)、JSON、XML、Hessian、Kryo(不支持跨语言);

 

选择什么序列化技术呢?(选型的关键点)

协议是否支持跨平台:

如果公司有多种语言进行混合开发,那么不适合用有语言局限性的序列化协议,否则JDK序列化出来的格式,其他语言无法支持;

序列化的速度:

如果序列化的频率非常高,那么选择序列化速度快的协议会为你的系统性能提升不少;

序列化出来的大小:

如果频繁的在网络中传输数据,那就需要数据越小越好,小的数据传输快,且不占带宽,也能整体提升系统性能;

Java是如何实现序列化的?(底层原理,API调用)

下面主要介绍Java如何进行序列化,以及序列化过程中需要注意的一些问题

Java实现序列化很简单,只需要实现Serializable接口即可

import java.io.*;

class User implements Serializable {//实现Serialiable接口
    //年龄
    private int age;
    //名字
    private String name;
    //get,set方法:
    public String getName(){
        return name;
    }

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

    public int getAge(){
        return age;
    }

    public void setAge(int age){
        this.age=age;
    }

}
public class Demo{
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //使用FileOutputStream文件字节输出流写入目标文件;
        FileOutputStream fileOutputStream=new FileOutputStream("C:\\Users\\橙汁\\Desktop\\github.txt");
        //序列化;
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(fileOutputStream);
        //创建user对象并赋值;
        User user=new User();

        user.setName("张三");
        user.setAge(20);

        //写入目标文件;
        objectOutputStream.writeObject(user);
        objectOutputStream.flush();
        objectOutputStream.close();
        //读取转换为对象;
        FileInputStream fis = new FileInputStream("C:\\Users\\橙汁\\Desktop\\github.txt");

        ObjectInputStream oin = new ObjectInputStream(fis);

        User user1 = (User) oin.readObject();

        System.out.println("name="+user1.getName()+"age="+user1.getAge());
    }
}

运行结果:name=张三age=20

以上把User对象进行二进制的数据存储后,并从文件中读取数据出来转成User对象就是一个序列化和反序列化的过程;

 

Java序列化中常见的问题

问题一:static属性不能被序列化

原因:序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量;

这里的不能序列化的意思,是序列化信息中不包含这个静态成员域

ObjectSerTest1 测试成功,是由于都在同一个机器(并且是同一个进程),由于这个jvm已经把count加载进来了(加载字节码文件.class),因此你获取的是加载好的count,若是你是传到另外一台机器或者你关掉程序重新写个程序读入test.obj,此时由于别的机器或新的进程是从新加载count的,因此count信息就是初始时的信息。转自:https://www.shangmayuan.com/a/150f3970d9b843a186589ced.html

问题二:Transient属性不会被序列化

往User类中添加私有瞬时Transient属性mood,setMood(”愉快“);
写入文件(序列化);
读出文件(反序列化);
mood=null
 
结果分析:原生类型为对应类型的默认值,包装类型为null,Transient属性不会被序列化;
 

问题三:序列化版本号serialVersionUID

所有实现序列化的对象都必须有个版本号,这个版本号可以由我们字节定义,当我们没有定义时,JDK工具会按照我们对象的属性生成一个对应的版本号;
 
版本号有什么用呢?
其实此版本号与我们平常的版本号一样,我们的软件版本号和官方的服务器版本不一致的话就告诉我们有新的功能更新了,主要用于提示用户进行更新;序列化也一样,我们的对象通常需要根据业务的需求变化要新增,修改或者删除一些属性,在我们做了一些修改后,就通过修改版本号来告诉反序列的那一方对象有了修改需要同步修改;
 
使用JDK生成的版本号和我们自定义的版本号的区别?
JDK工具生成的serialVersionUID是根据对象的属性信息生成的一个编号,这意味着只要对象的属性有一点变动那么他的序列化版本号就会同步进行改变;
此时,就像我们的软件一样使用JDK生成的serialVersionUID,只要对象由一丁点改变serialVersionUID就会随着变更,这样的话用户就得强制更新软件的版本,否则就用不了软件;
 
大多数的友好情况应该是这样:用户可以选择不更新,不更新的话用户只是无法体验新加(更新)所带来的功能而已;这种方式需要我们自定义版本号,这样的话,在新增了对象属性后不修改serialVersionUID,反序列化的时候只是无法拥有新加的属性,但并不影响程序运行;
 

代码测试:

对象序列化版本不同进行序列化和反序列化;

序列化版本号serialVersionUID=1;
class User implements Serializable {//实现Serialiable接口
    //年龄
    private int age;
    //名字
    static private String name;
    //Transient,瞬态属性;
    private transient String mood;
    private static final long serialVersionUID=1;

    //get,set方法:
    public String getName(){
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge(){
        return age;
    }
    public void setAge(int age){
        this.age=age;
    }
    public String getMood() { return mood; }
    public void setMood(String mood) { this.mood = mood; }

}
public class Demo{
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //使用FileOutputStream文件字节输出流写入目标文件;
        FileOutputStream fileOutputStream=new FileOutputStream("C:\\Users\\橙汁\\Desktop\\github.txt");
        //序列化;
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(fileOutputStream);
        //创建user对象并赋值;
        User user=new User();
        user.setName("张三");
        user.setAge(20);
        user.setMood("愉快");
        //写入目标文件;
        objectOutputStream.writeObject(user);
        objectOutputStream.flush();
        objectOutputStream.close();
}
修改序列化版本号serialVersionUID=2;
class User implements Serializable {//实现Serialiable接口
    //年龄
    private int age;
    //名字
    static private String name;
    //Transient,瞬态属性;
    private transient String mood;
    private static final long serialVersionUID=2;

    //get,set方法:
    public String getName(){
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge(){
        return age;
    }
    public void setAge(int age){
        this.age=age;
    }
    public String getMood() { return mood; }
    public void setMood(String mood) { this.mood = mood; }

}
public class Demo{
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        /*使用FileOutputStream文件字节输出流写入目标文件;
        FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\橙汁\\Desktop\\github.txt");
        //序列化;
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        //创建user对象并赋值;
        User user = new User();
        user.setName("张三");
        user.setAge(20);
        user.setMood("愉快");
        //写入目标文件;
        objectOutputStream.writeObject(user);
        objectOutputStream.flush();
        objectOutputStream.close();*/
        //读取转换为对象;
        FileInputStream fis = new FileInputStream("C:\\Users\\橙汁\\Desktop\\github.txt");
        ObjectInputStream oin = new ObjectInputStream(fis);
        User user1 = (User) oin.readObject();
        System.out.println("  name="+user1.getName()+"  age="+user1.getAge()+"  mood"+user1.getMood());
    }
}
运行结果:异常:Exception in thread "main" java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2;
反序列化异常,原因是对象序列化和反序列化的版本号不同导致;

对象新增属性,但是版本号相同也可以反序列化成功(新增SEX属性)
序列化的对象信息比序列化的对象少了一个新增的SEX属性,但serialVersionUID相同;
运行结果:name=张三age=20mood=null;(mood是瞬时属性,transient修饰)

对象新增属性,但是版本号是使用的JDK生成序列化版本号
运行结果:报错:Exception in thread "main" java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = -8448129994420994930, local class serialVersionUID = -7960089805662108428;

问题四:父类子类序列化问题;
序列化是以正向递归的形式进行的,如果父类实现了序列化那么其子类都将被序列化;
子类实现了序列化而父类没实现序列化,那么只有子类的属性会进行序列化,而父类的属性是不会进行序列化的;

父类没有实现序列化,子类实现序列化
父类
public class Parent{
//爱好
private String like;
}
子类
public class User extands Parent implements Serializable{
//年龄
private int age;
//名字
private String name;
}
序列化User之后再反序列化,最后执行结果:父类属性未被序列化

父类实现序列化,子类不实现序列化
父类
public class Parent implements Serializable{
//爱好
}
子类
public class User extends Parent{
//年龄
private int age;
//名字
private String name;
}
序列化之后再反序列化,最后执行结果:子类属性序列化正常;