java序列化与反序列化进阶(一)

Wesley13
• 阅读 859

一、readObject和writeObject

    通过上个章节的Java序列化与反序列化入门理解,对序列化和反序列化应该有了比较基本的认识。回顾一下,之前的序列化和反序列化,只是简单的处理,如果需要二次加工需要如何处理?比如序列化的时候需要对数据进行加密操作,对应的反序列化时候需要进行解密操作等。先看下面的例子:

package com.test;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Person implements Serializable{

    private static final long serialVersionUID = 3482314192692351792L;
    private int weight;
    private int age;
    private String name;
    public int getWeight() {
        return weight;
    }
    public void setWeight(int weight) {
        this.weight = weight;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    /**
     * 新增加的方法,序列化时会调用
     * @param stream
     * @throws IOException
     */
    private void writeObject(ObjectOutputStream stream)throws IOException{
        System.out.println("invoke writeObject......");
        //此时进行对象的序列化
        stream.defaultWriteObject();
        //额外序列化的数值,反序列化的时候会使用
        stream.writeInt(12345);
    }
    
    /**
     * 新增加的方法,反序列化时会调用
     * @param stream
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private void readObject(ObjectInputStream stream)throws IOException,ClassNotFoundException{
        System.out.println("invoke readObject......");
        //此时进行默认的序列化
        stream.defaultReadObject();
        System.out.print("默认初始化后的对象:");
        System.out.println(this);
        this.age = stream.readInt();
        System.out.print("对象的age进行加工后:");
    }
    
    @Override
    public String toString() {
        return "[weight:"+weight+",name:"+name+",age:"+age+"]";
    }
}

测试代码:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;

import com.test.Person;
import com.test.User;

public class TestPerson {

    /**
     * 序列化
     * 
     * @param filePath
     *            序列化要写入的文件路径
     * @throws Exception
     */
    public static void writeObject(String filePath) throws Exception {
        Person p = new Person();
        p.setAge(18);
        p.setName("testPersonName");
        p.setWeight(120);

        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(filePath));
            oos.writeObject(p);
            oos.flush();
        } finally {
            if (oos != null) {
                oos.close();
            }
        }
    }

    /**
     * 反序列化
     * 
     * @param filePath
     *            序列化的文件
     * @throws Exception
     */
    public static void readObject(String filePath) throws Exception {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(filePath));
            Person p = (Person) ois.readObject();
            System.out.println(p);
        } finally {
            if (ois != null) {
                ois.close();
            }
        }
    }

    public static void test(long num) {
        byte[] byteNum = new byte[8];
        for (int ix = 0; ix < 8; ++ix) {
            int offset = 64 - (ix + 1) * 8;
            byteNum[ix] = (byte) ((num >> offset) & 0xff);
        }
        System.out.println(Arrays.toString(byteNum));
    }

    public static void main(String[] args) throws Exception {
        String filePath = "f:/obj.out";
        writeObject(filePath);
        readObject(filePath);
    }
}

通过以上的例子可以看出,在序列化的时候程序执行了对象中定义的私有方法writeObject,反序列化的时候则执行了readObject。由于方法都是私有的,所以不难猜测,肯定是用了反射的方式实现。查看ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法即可发现反射的调用。

java序列化与反序列化进阶(一)

所以,通过在writeObject和readObject中二次处理,可以达到加工的目的。

二、Externalizable

    对象序列化可以继承Serializable,也可以继承Externalizable,两者有何区别呢?我们试着将上面Person的继承修改成Externalizable并运行,如下:

package com.test;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class Person implements Externalizable{

    private static final long serialVersionUID = 3482314192692351792L;
    private int weight;
    private int age;
    private String name;
    public Person(){
        System.out.println("invoke constructor!");
    }
    
    public int getWeight() {
        return weight;
    }
    public void setWeight(int weight) {
        this.weight = weight;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    /**
     * 新增加的方法,序列化时会调用
     * @param stream
     * @throws IOException
     */
    private void writeObject(ObjectOutputStream stream)throws IOException{
        System.out.println("invoke writeObject......");
        //此时进行对象的序列化
        stream.defaultWriteObject();
        //额外序列化的数值,反序列化的时候会使用
        stream.writeInt(12345);
    }
    
    /**
     * 新增加的方法,反序列化时会调用
     * @param stream
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private void readObject(ObjectInputStream stream)throws IOException,ClassNotFoundException{
        System.out.println("invoke readObject......");
        //此时进行默认的序列化
        stream.defaultReadObject();
        System.out.print("默认初始化后的对象:");
        System.out.println(this);
        this.age = stream.readInt();
        System.out.print("对象的age进行加工后:");
    }
    
    @Override
    public String toString() {
        return "[weight:"+weight+",name:"+name+",age:"+age+"]";
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        
    }
}

重新运行测试代码,结果如下:
invoke constructor!
invoke constructor!
[weight:0,name:null,age:0]

通过以上的例子,说明程序并没有进行序列化;另外,无参构造函数调用了两次,说明序列化和反序列化的时候各调用了一次。重新修改代码,使其可以序列化,如下:

package com.test;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class Person implements Externalizable{

    private static final long serialVersionUID = 3482314192692351792L;
    private int weight;
    private int age;
    private String name;
    public Person(){
        System.out.println("invoke constructor!");
    }
    
    public int getWeight() {
        return weight;
    }
    public void setWeight(int weight) {
        this.weight = weight;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    /**
     * 新增加的方法,序列化时会调用
     * @param stream
     * @throws IOException
     */
    private void writeObject(ObjectOutputStream stream)throws IOException{
        System.out.println("invoke writeObject......");
        //此时进行对象的序列化
        stream.defaultWriteObject();
        //额外序列化的数值,反序列化的时候会使用
        stream.writeInt(12345);
    }
    
    /**
     * 新增加的方法,反序列化时会调用
     * @param stream
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private void readObject(ObjectInputStream stream)throws IOException,ClassNotFoundException{
        System.out.println("invoke readObject......");
        //此时进行默认的序列化
        stream.defaultReadObject();
        System.out.print("默认初始化后的对象:");
        System.out.println(this);
        this.age = stream.readInt();
        System.out.print("对象的age进行加工后:");
    }
    
    @Override
    public String toString() {
        return "[weight:"+weight+",name:"+name+",age:"+age+"]";
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        this.name = (String)in.readObject();
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(this.name);
    }
}

此时name字段可以正常序列化。使用Externalizable时切记要提供无参构造!

三、readResolve方法的使用

根据测试,每次反序列的对象应该是为重新创建的,所以执行==操作时会返回false。那么如果我们需要对象是单例的情况下,该怎么操作呢?readResolve()可以很好的解决这个问题。先看一下未使用之前,Person类如下:

package com.test;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Person implements Serializable{

    private static final Person PERSON = new Person(1,2,"123");
    private static final long serialVersionUID = 3482314192692351792L;
    private int weight;
    private int age;
    private String name;
    public Person(){
        
    }
    
    public Person(int age,int weight,String name){
        this.age = age;
        this.weight = weight;
        this.name = name;
    }
    
    public static Person getSingleton(){
        return PERSON;
    }
    
    public int getWeight() {
        return weight;
    }
    public void setWeight(int weight) {
        this.weight = weight;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    /**
     * 新增加的方法,序列化时会调用
     * @param stream
     * @throws IOException
     */
    private void writeObject(ObjectOutputStream stream)throws IOException{
        //此时对静态的Person进行序列化
        stream.writeObject(PERSON);
    }
    
    /**
     * 新增加的方法,反序列化时会调用
     * @param stream
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private void readObject(ObjectInputStream stream)throws IOException,ClassNotFoundException{
            //反序列化Person
        stream.readObject();
    }
    
    @Override
    public String toString() {
        return "[weight:"+weight+",name:"+name+",age:"+age+"]";
    }
}

测试方法进行了调整,增加了对Person.PERSON和反序列化完的对象的比较:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;

import com.test.Person;
import com.test.User;

public class TestPerson {

    /**
     * 序列化
     * 
     * @param filePath
     *            序列化要写入的文件路径
     * @throws Exception
     */
    public static void writeObject(String filePath) throws Exception {
        Person p = new Person();
        p.setAge(18);
        p.setName("testPersonName");
        p.setWeight(120);

        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(filePath));
            oos.writeObject(p);
            oos.flush();
        } finally {
            if (oos != null) {
                oos.close();
            }
        }
    }

    /**
     * 反序列化
     * 
     * @param filePath
     *            序列化的文件
     * @throws Exception
     */
    public static void readObject(String filePath) throws Exception {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(filePath));
            Person p = (Person) ois.readObject();
            System.out.println(p == Person.getSingleton());
        } finally {
            if (ois != null) {
                ois.close();
            }
        }
    }

    public static void test(long num) {
        byte[] byteNum = new byte[8];
        for (int ix = 0; ix < 8; ++ix) {
            int offset = 64 - (ix + 1) * 8;
            byteNum[ix] = (byte) ((num >> offset) & 0xff);
        }
        System.out.println(Arrays.toString(byteNum));
    }

    public static void main(String[] args) throws Exception {
        String filePath = "f:/obj.out";
        writeObject(filePath);
        readObject(filePath);
    }
}

运行,结果为false,说明不是同一个对象。接下来对Person进行调整,新增readResolve方法,如下:

package com.test;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;

public class Person implements Serializable{

    private static final Person PERSON = new Person(1,2,"123");
    private static final long serialVersionUID = 3482314192692351792L;
    private int weight;
    private int age;
    private String name;
    public Person(){
        
    }
    
    public Person(int age,int weight,String name){
        this.age = age;
        this.weight = weight;
        this.name = name;
    }
    
    public static Person getSingleton(){
        return PERSON;
    }
    
    public int getWeight() {
        return weight;
    }
    public void setWeight(int weight) {
        this.weight = weight;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    /**
     * 新增加的方法,序列化时会调用
     * @param stream
     * @throws IOException
     */
    private void writeObject(ObjectOutputStream stream)throws IOException{
        //此时进行对象的序列化
        stream.writeObject(PERSON);
    }
    
    /**
     * 新增加的方法,反序列化时会调用
     * @param stream
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private void readObject(ObjectInputStream stream)throws IOException,ClassNotFoundException{
        stream.readObject();
    }
    
    private Object readResolve() throws ObjectStreamException {  
        //直接返回静态的Person
        return PERSON;  
    } 
    
    @Override
    public String toString() {
        return "[weight:"+weight+",name:"+name+",age:"+age+"]";
    }
}

此时再次执行测试代码,返回true,说明为同一个对象。同样的,查看ObjectInputStream,可以查看发射调用了readResolve方法。

上一篇:Java序列化与反序列化入门理解

下一篇:java序列化与反序列化进阶(二)

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
javaBean为什么要implements Serializable
一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才是可序列化的。因此如果要序列化某些类的对象,这些类就必须实现Serializable接口。而实际上,Serializable是一个空接口,没有什么具体内容,它的目的只是简单的标识一个类的对象可以被序列化。    什么情况下需要序列化:    1.     当
红烧土豆泥 红烧土豆泥
3年前
解决Redis序列化Java8的LocalDateTime问题
在从Redis获取带有LocalDateTime类型属性的对象时,产生序列化和反序列化问题解决办法方式一:实体类上指定LocalDateTime的序列化器和反序列化器java@JsonDeserialize(usingLocalDateTimeDeserializer.class)//反序列化@JsonSerialize(usingLo
Stella981 Stella981
3年前
Gson 数据解析
gson和其他现有javajson类库最大的不同时gson需要序列化的实体类不需要使用annotation来标识需要序列化的字段,同时gson又可以通过使用annotation来灵活配置需要序列化的字段。下面是一个简单的例子:1.public class Person {3.private String name;
Wesley13 Wesley13
3年前
unity 序列化和反序列化
什么是序列化和反序列化(1)序列化是指把对象转换为字节序列的过程,而反序列化是指把字节序列恢复为对象的过程;. (2)序列化:对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了对象的状态以及相关的描述信息。序列化机制
Stella981 Stella981
3年前
JFinal的Model进行json反序列化
在使用JFinal进行开发时,我们可能会需要解决这样的问题:Model进行json的序列化与反序列化。官方已经提供了序列化的方法Model.toJson()非常方便,反序列化就得自己实现一下了。之前我一直都是把Model序列化成的json字符串,反序列化成map,然后再调用Model.setAttrs(map)。这样就有类型转换问题,最后反序列化得到
Wesley13 Wesley13
3年前
Go 中 JSON 的序列化和反序列化
golang中对json的序列化/反序列化操作还是比较容易的,序列化操作主要是通过encoding/json包的Marshal()方法来实现,反序列化操作主要是通过encoding/json包的Unmarshal()方法来实现.//JSON序列化和反序列化//可用在api序列化输出//转成
Wesley13 Wesley13
3年前
unity序列化
什么是序列化unity的序列化在unity的开发中起着举重足轻的地位,许多核心的功能都是基于序列化和反序列化来实现的。序列化简单来讲就是就是将我们所要保存的数据进行二进制存储,然后当我们需要的时候,在读取二进制文件,反序列化回来。下面是一些常用的序列化的例子:存储脚本化的数据。在我们的c代码中,可以将我们所
Wesley13 Wesley13
3年前
Java序列化——transient关键字和Externalizable接口
  提到Java序列化,相信大家都不陌生。我们在序列化的时候,需要将被序列化的类实现Serializable接口,这样的类在序列化时,会默认将所有的字段都序列化。那么当我们在序列化Java对象时,如果不希望对象中某些字段被序列化(如密码字段),怎么实现呢?看一个例子:import java.io.Serializable;imp
Stella981 Stella981
3年前
FastJson 反序列化注意事项
问题描述使用fastJson对json字符串进行反序列化时,有几个点需要注意一下:反序列化内部类反序列化模板类0\.Getter/Setter问题如我们希望返回的一个json串为"name":"name","isDeleted":true,"isEmpty":1
Wesley13 Wesley13
3年前
Java并发编程:Java 序列化的工作机制
JDK内置同步器的实现类经常会看到java.io.Serializable接口,这个接口即是Java序列化操作,这样看来序列化也是同步器的一种机制。 关于序列化本文主要分析Java中的序列化机制,并看看AQS同步器的序列化,掌握序列化机制才能完整理解JDK内置的同步工具的实现。在程序中为了能直接以Java对象的形式进行保存,然后再