Serializable的序列化与反序列化

使用Serializable序列化,只要实现Serializable接口即可。一般情况下都会显示设置静态成员变量serialVersionUID为固定值。序列化时使用ObjectOutputStream写入,反序列化时使用ObjectInputStream读出。

如此简单,谁都会。但这是我碰到复杂点的情况,特作以下总结:

1、Serializable可继承:父类实现了序列化,子类也会自动实现序列化

PersonBean.java:

public class PersonBean implements Serializable{
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;

    public PersonBean(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;
    }

    @Override
    public String toString() {
        return name + "$$$" + age;
    }
}

ProgrammerBean.java:

public class ProgrammerBean extends PersonBean {
    private String language;

    public ProgrammerBean(String name, int age, String language) {
        super(name, age);
        this.language = language;
    }


    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    @Override
    public String toString() {
        return getName() + "@@@" + getAge() + "@@@" + language;
    }
}

SerialTest.java:

package com.example;

import com.example.serialBean.PersonBean;
import com.example.serialBean.ProgrammerBean;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * Created by Ralap on 2016/7/11.
 */
public class SerialTest {

    private static final String filePath = System.getProperty("user.dir");
    private static final String fileName = "serialTest.st";


    public static void test() {
        // 1、Serializable可继承
        testExtends();

        // 2、transient和static修饰变量,使其不参加序列化

        // 3、含有不可序列化的对象,自定义writeObject()和readObject()

        // 4、集合序列化

    }

    private static void testExtends() {
        PersonBean personBean = new PersonBean("Boss", 38);
        ProgrammerBean programmerBean = new ProgrammerBean("hacker", 29, "Java");

        printfn("==========Before Serialize :==========");
        printfn(personBean.toString());
        printfn(programmerBean.toString());

        File file = createFile();
        if (file == null) {
            printfn("create file fail");
            return;
        }

        // 序列化
        serialize(file, personBean, programmerBean);

        // 反序列化
        Object[] objs = new Object[2];
        unserialize(file, objs);
        PersonBean perBean = (PersonBean) objs[0];
        ProgrammerBean proBean = (ProgrammerBean) objs[1];

        // 打印
        printfn("==========After Unserialize:==========");
        printfn(perBean.toString());
        printfn(proBean.toString());
    }

    /**
     * 序列化
     *
     * @param file 序列化目标文件
     * @param objs 序列化对象
     */
    private static void serialize(File file, Object... objs) {
        ObjectOutputStream objectOS = null;
        try {
            FileOutputStream fos = new FileOutputStream(file);
            objectOS = new ObjectOutputStream(fos);
            for (Object obj : objs) {
                objectOS.writeObject(obj);
            }
            objectOS.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (objectOS != null) {
                try {
                    objectOS.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 反序列化
     * @param file 反序列化文件
     * @return 反序列化出来的对象数组
     */
    private static void unserialize(File file, Object[] objs) {

        ObjectInputStream objectIS = null;
        try {
            FileInputStream fis = new FileInputStream(file);
            objectIS = new ObjectInputStream(fis);
            Object obj;
            int offset = 0;
            while ((obj = objectIS.readObject()) != null) {
                objs[offset++] = obj;
                if (offset >= objs.length) {
                    return;
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (objectIS != null) {
                try {
                    objectIS.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void printf(String s) {
        System.out.printf("%s", s);
    }

    private static void printfn(String s) {
        System.out.printf("%s\n", s);
    }

    /**
     * 创建文件
     * 存在:删除后新建。不存在:新建
     */
    private static File createFile() {
        File file = new File(filePath, fileName);
        try {
            if (file.exists()) {
                if (!file.delete()) {
                    return null;
                }
            }
            if (!file.createNewFile()) {
                return null;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return file;
    }
}

结果:
这里写图片描述

2、static和transient修饰的变量,将不参加序列化

序列化只对类中的filed字段属性进行序列化,且序列化的只是类的实例对象的属性类型及值,所以method方法和static修饰的属性将不被序列化。如果要让非static属性也不序列化,使用transient。

扩展:另一个修饰变量符volatile,易变的意思。一般用于多线程中,因为每个线程都会有自己独立的内存空间,共享变量会从主内存拷贝一份到自己的内存中,操作的是自己内存中的数值,在进入线程或退出同步代码块时,才与共享中的成员变量进行比对、同步,这样可能导致其他线程获取到的不是最新值。所以,用volatile来修饰共享成员变量,在每次使用变量时都会强迫从共享内存中重新读取共享变量的值。

static:静态的,被所有对象共享,非某个对象私有,不会被持久化
transient:代表瞬间的意思,表示不会被持久化

把上面ProgrammerBean和SerialTest进行修改

ProgrammerBean.java

public class ProgrammerBean extends PersonBean {
    private String language;
    private static String belongIndustry = "Linux"; // static修饰
    transient private boolean hasGF; // transient修饰。boolean默认值是false,注意比较

    public ProgrammerBean(String name, int age, String language) {
        super(name, age);
        this.language = language;
    }


    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    public static String getBelongIndustry() {
        return belongIndustry;
    }

    public static void setBelongIndustry(String belongIndustry) {
        ProgrammerBean.belongIndustry = belongIndustry;
    }

    public boolean isHasGF() {
        return hasGF;
    }

    public void setHasGF(boolean hasGF) {
        this.hasGF = hasGF;
    }


    @Override
    public String toString() {
        return getName() + "^^^" + getAge() + "^^^" + language + "^^^" + belongIndustry + "^^^" + hasGF;
    }
}

SerialTest.java修改部分

    public static void test() {
        // 1、Serializable可继承
//        testExtends();

        // 2、transient和static修饰变量,使其不参加序列化
        testNotSerial();

        // 3、含有不可序列化的对象,自定义writeObject()和readObject()

        // 4、集合序列化

    }

    private static void testNotSerial() {
        PersonBean personBean = new PersonBean("Boss", 38);
        ProgrammerBean programmerBean = new ProgrammerBean("hacker", 29, "Java");
        programmerBean.setHasGF(true);

        printfn("==========Before Serialize :==========");
        printfn(personBean.toString());
        printfn(programmerBean.toString());

        File file = createFile();
        if (file == null) {
            printfn("create file fail");
            return;
        }

        // 序列化
        serialize(file, personBean, programmerBean);

        // 序列化后修改static属性值
        ProgrammerBean.setBelongIndustry("Android");

        // 反序列化
        Object[] objs = new Object[2];
        unserialize(file, objs);
        PersonBean perBean = (PersonBean) objs[0];
        ProgrammerBean proBean = (ProgrammerBean) objs[1];

        // 打印
        printfn("==========After Unserialize:==========");
        printfn(perBean.toString());
        printfn(proBean.toString());
    }

结果:
这里写图片描述

3、使用自定义序列化使不可序列化的类序列化

一些类没有实现Serializable,就不能序列化。如ProgrammerBean中包含了一个不可序列化的类InterestBean。

ProgrammerBean.java

public class ProgrammerBean extends PersonBean {
    private String languages;
    InterestBean interest;

    public ProgrammerBean(String name, int age, String languages) {
        super(name, age);
        this.languages = languages;
    }


    public String getLanguages() {
        return languages;
    }

    public void setLanguages(String languages) {
        this.languages = languages;
    }

    public InterestBean getInterest() {
        return interest;
    }

    public void setInterest(InterestBean interest) {
        this.interest = interest;
    }

    @Override
    public String toString() {
        return getName() + "^^^" + getAge() + "^^^" + languages + ":::" + interest.getType() + "^^^" + interest.getNames();
    }
}

InterestBean.java

public class InterestBean{
    private String type;
    private String names;

    public InterestBean(String type, String names) {
        this.type = type;
        this.names = names;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getNames() {
        return names;
    }

    public void setNames(String names) {
        this.names = names;
    }
}

SerialTest.java部分修改(从别人那学习到更漂亮的序列化代码,因此作了修改)

package com.example;

import com.example.serialBean.InterestBean;
import com.example.serialBean.ProgrammerBean;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * Created by Ralap on 2016/7/11.
 */
public class SerialTest {

    private static final String filePath = System.getProperty("user.dir");
    private static final String fileName = "serialTest.st";


    public static void test() {
        // 1、Serializable可继承
//        testExtends();

        // 2、transient和static修饰变量,使其不参加序列化
//        testNotSerial();

        // 3、含有不可序列化的对象,自定义writeObject()和readObject()
        testCustomSerial();

        // 4、集合序列化

    }

    private static void testCustomSerial() {
        ProgrammerBean programmerBean = new ProgrammerBean("hacker", 29, "Java");
        InterestBean interest = new InterestBean("gril", "Gril God");
        programmerBean.setInterest(interest);

        printfn("==========Before Serialize :==========");
        printfn(programmerBean.toString());

        File file = createFile();
        if (file == null) {
            printfn("create file fail");
            return;
        }

        // 序列化
        serialize(file, programmerBean);

        // 反序列化
        ProgrammerBean proBean = unserialize(file);

        // 打印
        printfn("==========After Unserialize:==========");
        printfn(proBean.toString());
    }

    /**
     * 序列化
     *
     * @param file 序列化目标文件
     * @param object 序列化对象
     */
    private static <T> void serialize(final File file, final T object) {
        ObjectOutputStream objectOS = null;
        try {
            FileOutputStream fos = new FileOutputStream(file);
            objectOS = new ObjectOutputStream(fos);
            objectOS.writeObject(object);
            objectOS.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (objectOS != null) {
                try {
                    objectOS.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 反序列化
     * @param file 反序列化文件
     * @return 反序列化出来的对象数组
     */
    private static <T> T unserialize(final File file) {
        ObjectInputStream objectIS = null;
        T retObj = null;
        try {
            FileInputStream fis = new FileInputStream(file);
            objectIS = new ObjectInputStream(fis);
            retObj =  (T)objectIS.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (objectIS != null) {
                try {
                    objectIS.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return retObj;
    }

}

上面在写入和读出的时候都会报不可序列化异常:NotSerializableException

如果这是自己的类,实现一下Serializable就OK了。但万一这是别人封装好的,不能修改,那怎么办?这时就可以使用自定义的序列化方法。在类中
【1】给该属性添加修饰符transient,为不可序列化。
【2】加上下面三个方法(一般前面两个就可以)并实现之:

// Serializable接口中没有抽象方法,这些方法不是重写接口的方法,且他们都是private,但序列化时会自动调用这里的方法。这就是机制
private void writeObject(ObjectOutputStream out) throws IOException {
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
}
private void readObjectNoData() throws ObjectStreamException
{
}

修改后的ProgrammerBean.java

public class ProgrammerBean extends PersonBean {
    private String languages;
    transient private InterestBean interest;

    public ProgrammerBean(String name, int age, String languages) {
        super(name, age);
        this.languages = languages;
    }


    public String getLanguages() {
        return languages;
    }

    public void setLanguages(String languages) {
        this.languages = languages;
    }

    public InterestBean getInterest() {
        return interest;
    }

    public void setInterest(InterestBean interest) {
        this.interest = interest;
    }

    @Override
    public String toString() {
        return getName() + "^^^" + getAge() + "^^^" + languages + ":::" + interest.getType() + "^^^" + interest.getNames();
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        // 先使用默认写入,会自动把可序列化的序列化
        out.defaultWriteObject();
        out.writeUTF(interest.getType());
        out.writeUTF(interest.getNames());
    }
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
        // 先使用默认读出,会自动把可序列化的读出
        in.defaultReadObject();
        // interest默认是null,这里必须新建。且读出与写入顺序必须一致
        interest = new InterestBean(in.readUTF(), in.readUTF()); 
    }
    private void readObjectNoData() throws ObjectStreamException
    {
        throw new InvalidObjectException("No Stream Data");
    }
}

这样结果就出来:
这里写图片描述

4、集合的序列化

ArrayList实现了Serializable接口,完全可以序列化。这个完全当做是验证测试

SerialTest.java部分修改

public class SerialTest {

    private static final String filePath = System.getProperty("user.dir");
    private static final String fileName = "serialTest.st";


    public static void test() {
        // 1、Serializable可继承
//        testExtends();

        // 2、transient和static修饰变量,使其不参加序列化
//        testNotSerial();

        // 3、含有不可序列化的对象,自定义writeObject()和readObject()
//        testCustomSerial();

        // 4、集合序列化
        testListSerial();
    }

    private static void testListSerial() {
        List<ProgrammerBean> list = new ArrayList<>();
        for (int i = 0; i < 5000; i++) {
            ProgrammerBean programmerBean = new ProgrammerBean("hacker" + i, 29, "Java");
            programmerBean.setInterest(new InterestBean("gril", "Gril God" + i));
            list.add(programmerBean);
        }

//        printfn("==========Before Serialize :==========");
//        printfn(programmerBean.toString());

        File file = createFile();
        if (file == null) {
            printfn("create file fail");
            return;
        }

        // 序列化
        serialize(file, list);

        // 反序列化
        List<ProgrammerBean> programmerList = unserialize(file);

        // 打印
        printfn("==========After Unserialize:==========");
        for (ProgrammerBean pro : programmerList) {
            printfn(pro.toString());
        }
    }

结果:
这里写图片描述
……
这里写图片描述

serialTest.st文件的大小:247 KB (252,987 字节)

我们目前使用的是XML(json因故暂时不考虑),XML也可以实现序列化与反序列化,具体有什么区别,还没研究,但先来简单计算下文件大小:

①Serializable中,UTF-8编码格式,都是英文字母或数字,每个字母或数字占一个字节,一个bean中的有意义的数据(属性值)大概是33个字节,33*5000 = 165,000字节。其他的占空间的都是包名+类名,属性类型。

②XML中,有意义的数据不变,也是165,000字节,其他的主要是tag占空间。保守假设每个tag名称为5字节,格式是<tag01>value</tag01>,也就是说每个属性值还要是15字节,每个bean共有6个属性。这样一个bean就是7*15 = 105字节,tag总空间:105*5000 = 525,000字节。

So,从大小上来说,还是Serializable节省空间。代码还不用写xml这样复杂的序列化与反序列化,看到那么多Bean,简单重复的操作,真想写个框架(反射+注解)改掉它。老大说xml执行效率高,也许吧,下次验证下就知道了。

下篇进攻XML……


补充(2016/7/12 18:00):
上面第一种情况是父类序列化,子类会自动实现序列化。如果父类不序列化,子类需要序列化。如上,PersonBean不实现Serializable,而ProgrammerBean实现Serializable,其他保持原样不变。这样,子类对象在序列化时正常,但反序列化时会报以下异常:

java.io.InvalidClassException: com.example.serialBean.ProgrammerBean; no valid constructor
    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
    at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1772)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)

这表示,在反序列化时检测的时候抛的异常,提示该类没有有效的构造方法。
解决办法:在父类中添加无参构造方法。具体原因,不详,百度也没找到满意的答案(希望哪位大神帮忙解惑下)。这让我想起了以前一位老师说过,永远给出无参构造方法。

猜你喜欢

转载自blog.csdn.net/a10615/article/details/51870609