java基础之序列化详解

来源:互联网 发布:python cdll 编辑:程序博客网 时间:2024/06/06 09:54

前言

java序列化这个从jdk1.1就已经被提出的概念,历经这么多版本升级,序列化在当前开发中依旧是必不可少。对于一些刚刚接触java的萌新,一提到序列化就会想到Serializable接口。除此之外,,,就一无所知了,当然我也是这样过来的,目前也只是局限于对于一些概念的理解,对于序列化这个强大工具发展演变不是那么熟悉,所以这里和大家一些来学习总结一下java开发基础之序列化!

序列化的作用

首先需要了解的就是为什么要序列化,序列化有什么作用。如果你都不知道一个工具是作用是什么,又何谈使用好这个工具?
在java中,万物皆对象。java程序能够运行起来都是大量对象一起互相协作来运行的,没有对象这些程序也就没法运行,而这个对象确有一个致命的缺陷,那就是离开了正在运行的程序就无法生存,比如说停电了,或者说程序运行终止了,这些保存信息的对象瞬间就会灰飞烟灭。这时候就会有人想,要是能将这些带有信息的对象停电之前保存在某个地方,等电恢复了,再将保存的对象恢复过来重新运行。于是就有人想出了序列化的概念,就是将那些对象以二进制的方式保存在本地磁盘中(或者其他存储中,例如数据库),然后当需要这些对象的时候,再从存储中恢复这些对象。是不是很方便??
当然序列化的作用远不止此,在当前分布式集群系统盛行于世的环境下。各个系统之前的远程调用便是必不可少的,而这些远程调用就会用到一些远程调用框架,像最近比较流行的阿里的dubbo,而在dubbo中依旧不可避免的在使用序列化来远程调用对象!目前dubbo默认调用的Hessain协议来进行序列化。当然可以采用其他的序列化方式,像json序列化,比如阿里的fastjson都是可以的,只不过性能上有所差别。所以序列化在分布式系统等远程调用中也是经常用到。学习和理解好序列化是必不可少的!

序列化的小例子

经过前面的介绍,想必大家对序列化也有所了解了,有用到序列化必不可少的就会用到反序列化,否则序列化就会变得毫无意义。反序列化就是上面提到的将存储在磁盘或者数据库中的二进制文件恢复成具体的java对象。这就是所谓的反序列化。话不多说,看看下面的例子。
被序列化的对象

/**  1. @ClassName: Student 2. @Description: TODO(测试被序列化的类) 3. @author 爱琴孩*/public class Student implements java.io.Serializable{    /**    * @Fields serialVersionUID : TODO(序列化版本号)    */     private static final long serialVersionUID = 1L;    private String name;    private Integer age;    private transient Integer weight;    public static Integer height;    public Student(){}    public Student(String name ,Integer age){        this.name=name;        this.age=age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Integer getAge() {        return age;    }    public void setAge(Integer age) {        this.age = age;    }    public Integer getWeight() {        return weight;    }    public void setWeight(Integer weight) {        this.weight = weight;    }    public static Integer getHeight() {        return height;    }    public static void setHeight(Integer height) {        Student.height = height;    }    public String toString(){        return "对象姓名是"+this.name+",年龄是"+this.age;       }}

对student对象进行序列化

/** 4. @ClassName: Client 5. @Description: TODO(测试将对象序列化保存到本地目录) 6. @author 爱琴孩*/public class Client {    public static void main(String[] args) {        File file=new File("D:"+File.separator+"student.txt");        if(!(file.exists())){            try {                file.createNewFile();            } catch (IOException e) {                e.printStackTrace();            }        }        ObjectOutputStream objectOutputStream= null;        try {            objectOutputStream=new ObjectOutputStream(new FileOutputStream(file));            Student s=new Student("爱琴孩",20);            Student.height=100;            s.setWeight(199);            objectOutputStream.writeObject(s);            objectOutputStream.close();        } catch (Exception e) {//这里由于简洁,就直接用Exception代替具体的异常            // TODO Auto-generated catch block            e.printStackTrace();        }    }}

对student进行反序列化

/**  7. @ClassName: InputClient 8. @Description: TODO(测试反序列化获取对象) 9. @author 爱琴孩*/public class InputClient {    public static void main(String[] args) {        File file=new File("D:"+File.separator+"student.txt");        ObjectInputStream  objectInputStream =null;        try {            Student.height=200;            objectInputStream=new ObjectInputStream(new FileInputStream(file));            Student s=(Student) objectInputStream.readObject();            System.out.println("反序列化之后的对象是"+s.toString());            System.out.println("被static修饰的属性反序列化之后的值是"+s.height);            System.out.println("被transient修饰的属性反序列化之后的值是"+s.getWeight());            objectInputStream.close();        } catch (Exception e) {            // TODO Auto-generated catch block            e.printStackTrace();        }       }}

反序列化测试结果如下

这里写图片描述
对于上面的小例子需要注意几点
1. 在上面的Student类中,实现Serializable接口的类,会有一个默认或者手动生成的一个不重复的序列号,对于这个序列化。如果在分布式系统中,存在两个一模一样的student类,一定要保证两边的序列化版本号是一致的。否则会导致反序列化不成功!
2. 在被序列化的类中,对于静态成员变量是属于类的,并不是某一个序列化的对象,所以上面的被static修饰的hieght反序列化之后的值,不是序列化之前的赋予的值。
3. 在被序列化的类中,被transient修饰的成员变量也是不会被序列化的,例如上面的weight成员变量,反序列化之后的属性值是被序列化属性对应类型的默认值,像是0,或者null.上面的weight反序列之后就是null。其实还有两外的一种方式也能达到相同的效果,比如一个类的一个成员变量不希望被序列化,这时候我们可以写一个基础父类,将这些不需要被序列化的成员变量放在父类中,父类不用实现Serializable接口,只需要子类实现Serializable接口就可以了,但是需要生成子类的一个对象必须先生成父类的对象,在反序列的生成对象也是这样。所以就需要利用父类的无参构造方法来生成父类的对象,也就是说父类必须要有一个无参的构造方法。

序列化的演进历程

在序列化概念刚刚被提出的时候,那是人们认为序列化已经很强大了,能够远程调用对象,那时候出现了RMI这个工具,但是RMI也是基于java平台的。随着技术以及应用的发展,跨平台的远程调用便出现了,例如,java和Python之间的远程调用,这时候java序列化不能跨平台的特性便无能为力了!同时由于web应用的快速发展,不可能为了实现java序列化的功能必须强制要求浏览器提供java运行环境。。所以java序列化的使用必将受到限制。下面是基于java序列化图解
这里写图片描述
由于java本身的序列化不能跨语言调用,于是强大的程序员们又找了另外的一个工具,那就是利用xml或者json来实现序列化。由于他们是对语言中立的,于是利用json,xml序列化迅速发展起来,成为潮流。下面是xml,json序列化图解
这里写图片描述
上面的xml和json序列化问题,虽然解决了java本身序列化的局限性,但是xml的复杂格式标签,无论是编写或者浏览起来都是比较麻烦的,尽管json序列化比较简洁,但是存储的空间还是比较大的,效率也有一定的局限性!于是人们又想起来了基于二进制文件的序列化方式,这种二进制无论是传输,还是存储都是比较优秀的,只不过跨平台不能解决。不过办法总是会有的,在现在的java发展中,很多以前不能实现的功能都是可以通过中间件来实现的,于是有人就打算用中间件来解决java序列化的跨平台问题。下面是利用中间件来实现的java序列化。
这里写图片描述
在上图中可以看到让这个中间层来定义/描述消息的格式,然后再弄一个小翻译器, 把这个程序员自定义的消息格式转换成各种语言的实现,例如java, python, c++等等。在转换好的语言实现里边,自动包含了要被序列化的类的定义, 以及实现序列化和反序列化的代码, 当然序列化以后的数据是二进制的。等到二进制的字节流通过网络传输到另外一台机器, 就可以反序列化为各种语言(例如Python)的对象了, 当然必须是同一个消息格式产生的Python类。

总结

现在的序列化技术发展很快,像阿里dubbo中hessain,还有Kryo或者FST,这些后续再继续学习总结!上面如有不足还请各位大佬,不吝赐教!