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