Java中的序列化和反序列化

一、序列化简介?

在项目中有很多情况需要对实例对象进行序列化与反序列化,这样可以持久的保存对象的状态,甚至在各个组件之间进行对象传递和远程调用。序列化机制是项目中必不可少的常用机制。

序列化: 对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存的java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。

反序列化: 客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。

总结:

  1. java序列化是指把java对象转换为字节序列的过程,而java反序列化是指把字节序列恢复为java对象的过程
  2. 序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态。

二、为什么序列化与反序列化?

当两个进程进行远程通信时,可以相互发送各种类型的数据,当两个java进行进行通信时,要传送对象,怎么传对象,通过序列化与反序列化。也就是说,发送方需要把对象转换为字节序列,然后在网络上传送,另一方面,接收方需要从字节序列中恢复出java对象。

序列化的好处:

  • 永久性保存对象,保存对象的字节序列到本地文件或者数据库中,实现了数据的持久化,通过序列化可以把数据永久的保存到硬盘上,
  • 利用序列化实现远程通信,可以在网络上传送对象的字节序列。
  • 在进程间传递对象

序列化的使用

Java 序列化方法可以分为两种:

  1. 实现 Serializable 接口:可以自定义 writeObject、readObject、writeReplace、readResolve 方法,会通过反射调用。
  2. 实现 Externalizable 接口:需要实现 writeExternal 和 readExternal 方法。
实现Serializable 接口

Java 的序列化使用起来很简单,实现 Serializable 接口即可,实际由 ObjectOutputStream、ObjectInputStream 完成对这个标记接口的处理。

public class Student implements Serializable {

    private static final long serialVersionUID = 4234796420521675233L;

    private String name;

    private int age;

    private String sex;

    private String phone;

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    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 String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}
//Student 测试类
public class SerializableMain {
	public static void main(String[] args) {
        Student student = new Student();
        student.setName("张三");
        student.setAge(30);
        student.setSex("男");
        student.setPhone("10086");

        serialize(student);
        Student s = (Student) deSerilize();
        System.out.println("姓名:" + s.getName()+"\n年龄:"+ s.getAge()+"\n性别:"+s.getSex()+"\n手机:"+s.getPhone());
    }

    //序列化
    public static void serialize(Student student){
        OutputStream outStream = null;
        ObjectOutputStream objectOutStream = null;

        File file = new File("D:/student.out");

        try {
            outStream = new FileOutputStream(file);
            objectOutStream = new ObjectOutputStream(outStream);
            objectOutStream.writeObject(student);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (objectOutStream != null){
                try {
                    objectOutStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outStream != null){
                try {
                    outStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //反序列化
    public static Object deSerilize(){
        Student student = new Student();
        InputStream inputStream = null;
        ObjectInputStream objectInputStream = null;
        File f = new File("D:/student.out");

        try {
            inputStream = new FileInputStream(f);
            objectInputStream = new ObjectInputStream(inputStream);
            student = (Student)objectInputStream.readObject();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            if(objectInputStream != null){
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return student;
    }
}

结果:
在这里插入图片描述

writeObject方法,readObject方法的使用

writeObject()是在ObjectOutputStream中定义的方法,使用这个方法可以将目标对象写入到流中,从而实现对象序列化。但是Java为我们提供了自定义writeObject()方法的功能,当我们在目标类中自定义writeObject()方法之后,将会首先调用我们自定义的方法,然后在继续执行原有的方法步骤(使用defaultWriteObject方法)。

该方法是与writeObject方法相对应的,是用于读取序列化内容的方法,其他功能与writeObject()类似。

private void writeObject(ObjectOutputStream objectOutStream) throws IOException {
        phone = phone+"-"+name;
        objectOutStream.defaultWriteObject();
    }

假如只自定义了writeObject()方法。
在这里插入图片描述
即定义了writeObject()方法,又定义了readObject()方法:

private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
        objectInputStream.defaultReadObject();
        phone = phone.split("-")[0];
    }

在这里插入图片描述

常见问题

(1)序列化 ID 的问题

两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 User 序列化为二进制数据再传给 B,B 反序列化得到 User。假如在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。

原因:
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致。Code-2 中,虽然两个类的功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化。

private static final long serialVersionUID = 4234796420521675233L;
(2)静态字段不会序列化

序列化时不保存静态变量,这是因为序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。

(3)transient关键字的使用

transient 关键字有两个特性:

  1. 凡是被该关键字修饰的字段,都将被序列化过滤掉,即不会被序列化。
  2. transient 修饰过的成员反序列化后将赋予默认值,即 0 或 null。
自定义序列化接口:Externalizable

不像 Serializable 接口只是一个标记接口,里面的接口方法都是可选的(可实现可不实现,如果不实现则启用其自动序列化功能),而 Externalizable 接口不是一个标记接口,它强制你自己动手实现串行化和反串行化算法。

public interface Externalizable extends java.io.Serializable {
	// 串行化
    void writeExternal(ObjectOutput out) throws IOException;
    // 反串行化
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
总结

1、果类a仅仅实现了Serializable接口:

  • ObjectOutputStream采用默认的序列化方式,对a对象的非transient实例变量进行序列化
  • ObjectInputStream采用默认的反序列化方式,对a对象的非transient实例变量进行反序列化

2、如果类a仅仅实现了Serializable接口,并且还定义了a对象的writeObject(ObjectOutputStream out) 和readObject(ObjectInputStream in):

  • ObjectOutputStream调用a对象的writeObject(ObjectOutputStream out)的方法进行序列化
  • ObjectInputStream调用a对象的readObject(ObjectInputStream in)的方法进行序列化

3、如果a类实现了ExternaInalizable接口,且User类必须实现readExternam(ObjectInput in)和wiriteExternal(ObjectOutput out)方法:

  • ObjectOutputStream调用a对象的wiriteExternal(ObjectOutput out)的方法进行序列化
  • ObjectInputStream调用a对象的readExternam(ObjectInput in)的方法进行序列化
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值