java-序列化与反序列化

作者: snoweek | 来源:发表于2016-05-08 18:20 被阅读218次

    序列化和反序列化的概念

    1. 序列化:把java对象转换为字节序列的过程称为对象的序列化,这些字节序列可以被保存在磁盘上或通过网络传输,以备以后重新恢复原来的对象
    2. 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。序列化机制使得对象可以脱离程序的运行而独立存在

    序列化的功能/用途

    1. 持久化对象:Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,
      这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中,以此实现该功能 。java中的对象的内部状态只保存在内存中,其生命周期最长与JVM的生命周期一样,即JVM停止之后,所有对象都会被销毁。
    2. 网络传输:在网络上传送对象的字节序列。

    实际应用

    1. 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。
    2. 当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

    实现

    1. java.io.Serializable接口,那么它就可以被序列化
    2. Externalizable:
      Serializable接口
      · 优点:内建支持
      · 优点:易于实现
      · 缺点:占用空间过大
      · 缺点:由于额外的开销导致速度变比较慢
      Externalizable接口
      · 优点:开销较少(程序员决定存储什么)
      · 优点:可能的速度提升
      · 缺点:虚拟机不提供任何帮助,也就是说所有的工作都落到了开发人员的肩上。
      在两者之间如何选择要根据应用程序的需求来定。Serializable通常是最简单的解决方案,但是它可能会导致出现不可接受的性能问题或空间问题;在出现这些问题的情况下,Externalizable可能是一条可行之路。

    JDK类库中的序列化API

    1. java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
    2. java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

    序列化与反序列化的编程实现

    实现序列化接口的类

    public class Person implements Serializable{
        private static final long serialVersionUID = -1015228989208411177L;
        private String name;   
            private  int age;  
        public Person(String name, int age) {    
            this.name = name;   
            this.age = age;   
        }   
        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 WriteObject {
        public static void main(String[] args) {
            ObjectOutputStream oos = null;   
            try {   
                //1.创建一个ObjectOutputStream   
                oos = new ObjectOutputStream(new FileOutputStream("/home/sunyan/object.txt"));   
                Person per = new Person("孙悟空", 500);  
                //2.将per对象写入输入流   
                oos.writeObject(per); 
            } catch (FileNotFoundException e) {   
                 e.printStackTrace();   
            } catch (IOException e) {   
                 e.printStackTrace();   
            }finally{   
                try {   
                    if(oos != null){   
                        oos.close();   
                    }   
                } catch (IOException e) {   
                     e.printStackTrace();   
                }   
            }   
        }
    }
    

    反序列化

    public class ReadObject {
        public static void main(String[] args) {
            ObjectInputStream ois = null;              
            try {   
                //1.创建一个ObjectInputStream输入流   
               ois = new ObjectInputStream(new FileInputStream("/home/sunyan/object.txt"));   
                //2.从输入流中读取一个Java对象,并将其强制类型转换为Person对象   
                Person p = (Person) ois.readObject();
                System.out.println("名字为:" + p1.getName() + "\n年龄为:" + p1.getAge());
            } catch (FileNotFoundException e) {   
                e.printStackTrace();   
            } catch (IOException e) {   
                e.printStackTrace();   
            } catch (ClassNotFoundException e) {   
                e.printStackTrace();   
            }finally{   
                try {   
                    if (ois == null) {   
                         ois.close();   
                    }   
                } catch (IOException e) {   
                    e.printStackTrace();   
                }   
            }   
        }
    }
    

    执行结果:

    1. 如果我们向文件中使用序列化机制写入了多个Java对象,使用反序列化机制恢复对象必须按照实际写入的顺序读取。
      序列化
     Person per1 = new Person("孙悟空", 500);  
     Person per2 = new Person("孙小妹", 50); 
    oos.writeObject(per1); 
     oos.writeObject(per2); 
    

    反序列化

    Person p1 = (Person) ois.readObject();
    Person p2 = (Person) ois.readObject();   
    System.out.println("名字为:" + p1.getName() + "\n年龄为:" + p1.getAge());
    System.out.println("名字为:" + p2.getName() + "\n年龄为:" + p2.getAge()); 
    

    执行结果


    1. 对象引用的序列化
      如果类的属性不是基本类型或者String类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则该类也是不可序列化的,即使该类实现了Serializable,Externalizable接口。
    public class Teacher implements Serializable{
        private String name;   
        //类的属性是引用类型,也必须序列化。   
        //如果Person是不可序列化的,无论Teacher实现Serializable,Externalizable接口,则Teacher   
        //都是不可序列化的。   
        private Person student;   
        public Teacher(String name, Person student) {   
            super();   
            this.name = name;   
            this.student = student;   
        }   
        public String getName() {   
             return name;   
        }    
        public void setName(String name) {   
             this.name = name;   
        }      
        public Person getStudent() {   
             return student;   
        }    
        public void setStudent(Person student) {   
            this.student = student;   
        }   
    }
    

    上述代码中,Teacher中有一个引用类型student,若Person未实现接口Serializable。即

    public class Person implements Serializable{
    }
    

    则在序列化过程中,会报错


    1. Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
      在Person中更改如下代码
    private  transient int age; 
    

    此时1中的代码,经序列化和反序列化后,执行结果如下


    1. s​e​r​i​a​l​V​e​r​s​i​o​n​U​I​D
      s​e​r​i​a​l​V​e​r​s​i​o​n​U​I​D​:​ ​字​面​意​思​上​是​序​列​化​的​版​本​号​,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量。序列化 ID 是否一致,决定 虚拟机是否允许反序列化。
      实现Serializable接口的类如果类中没有添加serialVersionUID,那么就会出现如下的警告提示



      serialVersionUID有两种生成方式:
      采用 Add default serial version ID
      这种方式生成的serialVersionUID是1L,例如:

     private static final long serialVersionUID = 1L;
    

    采用 Add generated serial version ID这种方式生成的serialVersionUID是根据类名,接口名,方法和属性等来生成的,例如:

    private static final long serialVersionUID = -1015228989208411177L;
    

    对1中的代码序列化后,修改

    private static final long serialVersionUID = -1015228989208411177L;
    

    private static final long serialVersionUID = -1015228989208411178L;
    

    此时,进行反序列化,会报错


    反序列化漏洞危害

    当应用代码从用户接受序列化数据,并试图反序列化改数据进行下一步处理时,会产生反序列化漏洞,其中最有危害性的就是远程代码注入。
    这种漏洞产生原因是,java类ObjectInputStream在执行反序列化时,并不会对自身的输入进行检查,这就说明恶意攻击者可能也可以构建特定的输入,在 ObjectInputStream类反序列化之后会产生非正常结果,利用这一方法就可以实现远程执行任意代码。

    最后再加一些相关知识点

    1、声明为static和transient的成员数据不能被串行化,因为static代表类的状态,transient代表对象的临时数据。

    2、要想将父类对象也序列化,就需要让父类也实现Serializable 接口。
    3、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

    相关文章

      网友评论

        本文标题:java-序列化与反序列化

        本文链接:https://www.haomeiwen.com/subject/kfbsrttx.html