Java 序列化与反序列化详解

一、简介

序列化:把对象存储为二进制格式(字节序列)的过程(我们看不懂,但是计算机能够识别这些字节序列)。

反序列化:把二进制格式(字节序列)还原为相应的对象的过程。

二、序列化的应用场景

实际工作中用到序列化的场景主要有两个:

【a】对象持久化:  对象持久化操作的时候,可以保存在文件也可以保存在数据库中,常见的都是保存在数据库中。

【b】网络传输:  当对象需要在网络上传输的时候,也是需要序列化对象的。

三、序列化与反序列化的实现

在Java中实现序列化与反序列化主要使用到的类以及方法有: 

【a】需要序列化的类必须实现Serializable接口。

【b】使用ObjectOutputStream的writeObject()方法将对象序列化为二进制文件。

【c】使用ObjectInputStream的readObject()方法将二进制文件反序列化为相应的对象。

下面,通过具体的示例讲解序列化与反序列化的实现:

(1)、定义一个实体类(这里假设为会员实体类Member.java),实现Serializable接口

/**
 * @Description: 会员实体类
 * @Author: weishihuai
 * @Date: 2018/11/4 19:41
 * 说明: 实现序列化接口Serializable
 */
public class Member implements Serializable {
    /**
     * 会员ID
     */
    private String memberId;
    /**
     * 会员姓名
     */
    private String name;
    /**
     * 体重(注意: 这里使用了transient关键字修饰)
     */
    private transient Double weight;
    /**
     * 身高(注意: 静态属性、类属性)
     */
    private static Double height;

    public Member(String memberId, String name, Double weight) {
        this.memberId = memberId;
        this.name = name;
        this.weight = weight;
    }

    public String getMemberId() {
        return memberId;
    }

    public void setMemberId(String memberId) {
        this.memberId = memberId;
    }

    public String getName() {
        return name;
    }

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

    public Double getWeight() {
        return weight;
    }

    public void setWeight(Double weight) {
        this.weight = weight;
    }

    public static Double getHeight() {
        return height;
    }

    public static void setHeight(Double height) {
        Member.height = height;
    }

    @Override
    public String toString() {
        return "Member{" +
                "memberId='" + memberId + '\'' +
                ", name='" + name + '\'' +
                ", weight=" + weight +
                '}';
    }
}

(2)、编写序列化与反序列化操作的类SerializableDemo.java:

/**
 * @Description: 序列化与反序列化
 * @Author: weishihuai
 * @Date: 2018/11/4 19:45
 * 说明:
 * 序列化: 输出流 ObjectOutputStream
 * 反序列化: 输入流 ObjectInputStream
 * <p>
 * 1. 先序列化再反序列化
 * 2. 不是所有的对象都可以序列化与反序列化
 * 3. 不是所有的属性都需要序列化,不需要序列化的属性使用transient关键字修饰
 */
public class SerializableDemo {

    public static void main(String[] args) {
        //将对象序列化到d:/aaa/member.txt中
        writeObjectToBinary("d:/aaa/member.txt");
        //将d:/aaa/member.txt中的二进制文件还原为实体对象
        Member member = readObjectFromBinary("d:/aaa/member.txt");
        System.out.println(member);
    }

    /**
     * 序列化操作
     *
     * @param destPath 序列化后存放二进制文件的目标路径
     */
    public static void writeObjectToBinary(String destPath) {
        //1. 创建需要序列化的对象
        Member member = new Member(UUID.randomUUID().toString(), "zhangsan", 60.5D);
        ObjectOutputStream oos = null;
        try {
            //2. 建立与输出文件的路径的联系
            oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(new File(destPath))));
            //3. 使用ObjectOutPutStream的writeObject()方法将对象序列化到文件
            oos.writeObject(member);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4. 关闭流
            if (null != oos) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 反序列化操作
     *
     * @param sourcePath 反序列化时二进制文件的源路径
     * @return 反序列化后的对象
     */
    public static Member readObjectFromBinary(String sourcePath) {
        //1. 建立与源二进制文件的联系
        File file = new File(sourcePath);
        ObjectInputStream ois = null;

        try {
            ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)));
            // 2. 使用ObjectInputStream的readObject()方法将二进制文件还原为实体对象
            Object object = ois.readObject();
            // 3. 判断是否为Member类型
            if (object instanceof Member) {
                return (Member) object;
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 4. 关闭流
            if (null != ois) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

}

(3)、运行程序:结果如下图

以上是Member对象序列化后变成的字节序列(二进制格式),计算机能够识别这些二进制格式的文件,我们并看不懂文件。

以上是将二进制格式的文件反序列化为Member对象之后输出的属性,根据输出可以看到:

使用关键字transient修饰的属性不会进行序列化操作,反序列化后这些属性为null或者0。

(4)、测试静态属性height是否能够序列化

public class SerializableDemo {

    public static void main(String[] args) {
        //将对象序列化到d:/aaa/member.txt中
        writeObjectToBinary("d:/aaa/member.txt");
    }
}

【a】首先执行序列化操作,这时候我们序列化进去的height属性的值为180.5

 private static Double height = 180.5D;

【b】然后我们修改height属性的值为165.5,

private static Double height = 165.5D;

【c】这时候我们进行反序列化操作

public class SerializableDemo {

    public static void main(String[] args) {
        //将d:/aaa/member.txt中的二进制文件还原为实体对象
        Member member = readObjectFromBinary("d:/aaa/member.txt");
        System.out.println(member);
    }
}

【d】控制台输出:

由图可见,反序列化之后的member对象中height静态属性的值为165.5,并不是我们序列化时保存的值180.5,由此可以证明静态属性不进行序列化。

四、注意点

【a】实体类必须实现序列化接口Serializable,否则会报错。

我们先去掉Member实体类的implements Seriablizable,执行序列化操作,发现控制台报错。

当你看到这个报错信息的时候,就应该知道是少了实现Seriablizable接口。

【b】序列化ID (serialVersionUID)的问题

(1)  首先我们执行序列化操作

public class SerializableDemo {

    public static void main(String[] args) {
        //将对象序列化到d:/aaa/member.txt中
        writeObjectToBinary("d:/aaa/member.txt");
    }
}

(2)  然后我们去修改Member.java,给它加多一个属性mobile:

public class Member implements Serializable {
    /**
     * 会员ID
     */
    private String memberId;
    /**
     * 会员姓名
     */
    private String name;
    /**
     * 体重(注意: 这里使用了transient关键字修饰)
     */
    private transient Double weight;
    /**
     * 身高(注意: 静态属性、类属性)
     */
    private static Double height = 165.5D;
    /**
     * 手机号码
     */
    private String mobile;

    //setter getter toString
}

(3) 然后执行反序列化操作

public class SerializableDemo {

    public static void main(String[] args) {
        //将d:/aaa/member.txt中的二进制文件还原为实体对象
        Member member = readObjectFromBinary("d:/aaa/member.txt");
        System.out.println(member);
    }
}

(4) 运行结果

由图可见,反序列化的时候报错了。原因就是因为序列化与反序列化时的serialVersionUID不一致导致。

(5) 解决方法

给实体类Member.java指定serialVersionUID,这样序列化与反序列化的serialVersionUID一致,即使序列化后修改了实体类,反序列化也不会报错。可以简单的指定为1,当然也可以使用随机数。

private static final long serialVersionUID = 1L;

public class Member implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 会员ID
     */
    private String memberId;
    /**
     * 会员姓名
     */
    private String name;
    /**
     * 体重(注意: 这里使用了transient关键字修饰)
     */
    private transient Double weight;
    /**
     * 身高(注意: 静态属性、类属性)
     */
    private static Double height = 165.5D;
    /**
     * 手机号码
     */
    private String mobile;
}

(6)重复以上步骤,发现序列化后,然后修改实体类,然后反序列化没有报错了。

 

五、总结

以上是关于Java中序列化以及反序列化的方法,以及通过示例讲解了如何实现序列化反序列化,还有谈了一些实际工作中需要注意的一些问题。本文是作者在复习IO操作ObjectInputStream和ObjectOutputStream的时候,顺便研究了下序列化以及反序列化操作,本文只是作者的一些见解与总结,仅供大家学习参考,希望能对大家有所帮助!

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/83718960