Java序列化(Serializable)与反序列化详解

什么是序列化?

Java序列化是在JDK 1.1中引入的,是Java内核的重要特性之一。
Java序列化API允许我们将一个对象转换为流,并通过网络发送,或将其存入文件或数据库以便未来使用,
反序列化则是将对象流转换为实际程序中使用的Java对象的过程。

序列化有啥用?

1.暂存大对象
2.Java对象需要持久化的时候
3.需要在网络,例如socket中传输Java对象
    因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收  端读到二进制数据之后反序列化成Java对象
4.深度克隆(复制)
5.跨虚拟机通信

代码怎么写?


1.序列化例子

实体类:Student.java

package com.dylan.serialization;

import java.io.Serializable;

/**
 * 学生类
 *
 * @author xusucheng
 * @create 2018-01-08
 **/
public class Student implements Serializable {

    private int id;
    private String name;
    private String gender;

    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;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Student(int id, String name, String gender) {
        this.id = id;
        this.name = name;
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                '}';
    }
}
package com.dylan.serialization;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 序列化例子
 *
 * @author xusucheng
 * @create 2018-01-08
 **/
public class SerializationDemo {
    public static void main(String[] args) throws IOException{
        //学生1
        Student s1 = new Student(101,"Jack","Male");
        //学生2
        Student s2 = new Student(102,"Lily","Female");

        List<Student> list = new ArrayList<>();
        list.add(s1);
        list.add(s2);

        //创建文件流
        FileOutputStream fos = new FileOutputStream("D:\\test\\student01.ser");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(list);

        os.close();
        System.out.println("序列化完成!");
    }
}




2.反序列化例子

package com.dylan.serialization;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 反序列化例子
 *
 * @author xusucheng
 * @create 2018-01-08
 **/
public class DeserializationDemo {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("D:\\test\\student01.ser");
        ObjectInputStream is = new ObjectInputStream(fis);
        Object obj = null;
        List<Student> list = new ArrayList<>();
        try {
            list = (List<Student>)is.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        is.close();

        //遍历list,输出
        for (Student student:list){
            System.out.println(student.toString());
        }
    }
}



3.serialVersionUID的作用

可以看到上面序列化例子中的学生实体类只是实现了Serializable接口,假如我现在接到客户需求,要调整Student类,增加学生年龄属性,调整后如下:

public class Student implements Serializable {

    private int id;
    private String name;
    private String gender;
    private int age;
...
此时,我在去执行反序列化程序,结果报错了:


啥玩意?就是说Stuent类调整后,其序列化版本ID发生了变化,java又是根据这个ID来判断是不是同一个类。ID不一样则无法反序列化!

事实上如果一个类没有定义serialVersionUID,它会自动计算出来并分配给该类。当发生以下情况,这个ID就会改变:

  • 在类中添加一些新的变量。
    将变量从transient转变为非tansient,对于序列化来说,就像是新加入了一个变量而已
    将变量从静态的转变为非静态的,对于序列化来说,就也像是新加入了一个变量而已

这个时候就需要我们手动指定这个serialVersionUID了,它告诉jvm,嗨,这个学生类我更新了一下,反序列化的时候记得尽量向后兼容,别报错。通常我们有以下几种方式:

serialVersionUID生成方式:

1)直接写固定的1L;

2)利用JDK自带serialver命令:切到类路径下


3)IDEA生成serialVersionUID

Intellij IDEA 默认没启用这个功能

Preferences->Editor->Inspections->Serialization issues->Serializable class without serialVersionUID 勾上
应用之后就开启了检测功能

在你的class名前(一定是光标移动到类名前):按(mac:option+return)或者( win:Alt+Enter) 就会提示自动创建 serialVersionUID 了。


加上serialVersionUID后的实体类如下:

public class Student implements Serializable {

    private static final long serialVersionUID = -2110227637642817458L;

    private int id;
    private String name;
    private String gender;
    private int age;
...
再次执行反序列化又恢复正常了:

Student{id=101, name='Jack', gender='Male'}
Student{id=102, name='Lily', gender='Female'}

在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。

那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。


4.static和transient属性无法序列化

transient翻译过来就是短暂的,瞬时的。如果在可序列化的类中某个成员变量加了这个修饰,则说明它只在jvm运行时才保存值,序列化时不会保存的。

为了证明这一点,我们调整一下Student类,加入1个静态变量mess和1个transient变量password。调整后的类如下:

package com.dylan.serialization;

import java.io.Serializable;

/**
 * 学生类
 *
 * @author xusucheng
 * @create 2018-01-08
 **/
public class Student implements Serializable {

    private static final long serialVersionUID = -2110227637642817458L;
    private static String mess="null";

    private int id;
    private String name;
    private String gender;
    private int age;
    private transient String password;

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    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;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Student(int id, String name, String gender) {
        this.id = id;
        this.name = name;
        this.gender = gender;
        mess = "something";
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                ", password='" + password + '\'' +
                ", mess='" + mess + '\'' +
                '}';
    }
}

再次执行序列化,反序列化后:

结论没问题。


5.序列化实战:Socket传输序列化对象例子

package com.dylan.serialization;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author xusucheng
 * @create 2018-01-08
 **/
public class Server {
    public static void main(String[] args) {

        //The client is used to handle connections with a client once a connection is
        //established.
        Socket client = null;

        //The following two objects handles our Serialization operations, ObjectOutputStream
        //writes an object to the stream. ObjectInputStream reads an object from the stream.
        ObjectOutputStream out = null;
        ObjectInputStream in = null;

        try {
            ServerSocket server = new ServerSocket(8888);
            client = server.accept();
            out = new ObjectOutputStream(client.getOutputStream());
            in = new ObjectInputStream(client.getInputStream());

            Student student = (Student) in.readObject();
            System.out.println(student);

            // close resources
            out.close();
            in.close();
            client.close();
            server.close();

        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }
        catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }
}

package com.dylan.serialization;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * @author xusucheng
 * @create 2018-01-08
 **/
public class Client {
    public static void main(String[] args) {
        Socket client = null;
        ObjectOutputStream out = null;
        ObjectInputStream in = null;

        try {

            client = new Socket("127.0.0.1", 8888);
            out = new ObjectOutputStream(client.getOutputStream());
            in = new ObjectInputStream(client.getInputStream());

            Student student = new Student(991,"Dylan","Male");
            out.writeObject(student);
            out.flush();

            out.close();
            in.close();
            client.close();

        } catch (UnknownHostException e) {
            e.printStackTrace();
            System.exit(1);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }

    }
}

说明:假如Student类没有实现序列化接口,在执行Client程序时会报错:


正如我们前面所讲,网络只能传输二进制数据,Java对象要想传输必须先序列化。


运行效果:




以上只是讲解了一下序列化的常用知识点,还有些深入的部分,以后有空再总结。





posted @ 2018-01-08 19:50  一锤子技术员  阅读(7)  评论(0编辑  收藏  举报  来源