Java I/O之对象序列化与反序列化

摘要

序列化与反序列化是对象持久化、数据传输过程中的常用手段,应用于多种重要的底层场景中。本文主要讨论了Java平台中序列化与反序列化相关的内容。更加详细的内容,请参考本文最后给出的相关链接。

简介

  • 第一部分 Java如何实现对象的序列化与反序列化
  • 第二部分 Transient关键字在对象序列化与反序列化中的作用
  • 第三部分 ArrayList类序列化与反序列化实现的优劣
  • 第四部分 Java序列化与反序列化过程中类继承体系中构造函数调用问题

(一) 对象序列化与反序列化

在进行对象持久化或者是远程数据传输时,经常进行对象的序列化与反序列化操作,Java对象的序列化与反序列化操作关注的Java对象的“状态”,也就是对象的成员变量的状态(内容,或值),这也就表明了序列化与反序列化,不会关注静态变量(static关键字修饰)

在Java中,实现了java.io.Serializable接口的类就可以被序列化。在进行对象序列化与反序列化操作时,我们用到的基本类是java.io.ObjectInputStream和java.io.ObjectOutputStream

如下列代码所示:

//实现接口
public class User implements Serializable{
    private static final long serialVersionUID = 1L;
    private static int num = 1;
    private String name;
    private int age;
    private String sex;
    public User(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        num ++;
        print();
    }
    public void print(){
        toString(); // 本类的构造函数中不能够调用本类的重载的toString()方法
        System.out.println(num);
    }
    @Override
    public String toString() {
        return "User [name=" + name + ", age=" + age + ", sex=" + sex + "]";
    }
}
//测试
public class ObjectSerializableTest {
    public static void main(String[] args) throws Exception {
        String path = "res\\ost.dat";
        System.out.println("**********************序列化 Duaghter**********************");
        output(path, new User("Bruce", 23, "男"));
        System.out.println("**********************反序列化 Duaghter**********************");
        input(path);
    }
    public static void input(String path) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
        Object obj = ois.readObject();
        System.out.println(obj);
        ois.close();
    }
    public static void output(String path, Object obj) throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
        oos.writeObject(obj);
        oos.flush();
        oos.close();
    }
}

测试结果:

**********************序列化 Duaghter**********************
2
**********************反序列化 Duaghter**********************
User [name=Bruce, age=23, sex=男]

我们发现,在进行序列化与反序列化时,静态变量并没有被关注。

(二)Trainsient关键字

在进行对象序列化时,默认情况下,JVM不对Trainsient关键字修饰的成员变量进行序列化操作,但是我们仍旧可以自己完成相关变量的序列化操作。同时,由于Transient关键字的修饰,某些情况写能够提高程序性能,这在后面论及ArrayList实现时会有所提及。

下面代码展示了,Transient关键字的作用:

//实现接口
public class User implements Serializable{
    private static final long serialVersionUID = 1L;
    private static int num = 1;
    private String name;
    private int age;
    private transient String sex; // tansient修饰
    public User(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        num ++;
        print();
    }
    public void print(){
        toString(); // 本类的构造函数中不能够调用本类的重载的toString()方法
        System.out.println(num);
    }
    @Override
    public String toString() {
        return "User [name=" + name + ", age=" + age + ", sex=" + sex + "]";
    }
}
//测试代码如上

运行结果

**********************序列化 Duaghter**********************
2
**********************反序列化 Duaghter**********************
User [name=Bruce, age=23, sex=null]

(三)ArrayList的源码分析

首先,我们给出着重关注点的相关源代码。java.util.ArrayList

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = {};
    transient Object[] elementData; // non-private to simplify nested class access
    private int size;
    .... //省略代码
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }
}

首先,我们猜想: 由于elementData成员变量被Ttransient关键字修饰,那么其肯定不能够被序列化,size成员变量可以被序列化。但是实际情况,并非如此,我们执行如下代码:

public class ObjectSerializableTest {
    public static void main(String[] args) throws Exception {
        String path = "res\\ost.dat";
        List<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("world");
        System.out.println("**********************序列化 Duaghter**********************");
        output(path, list);
        System.out.println("**********************反序列化 Duaghter**********************");
        input(path);
    }
    public static void input(String path) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
        Object obj =  ois.readObject();
        System.out.println(obj);
        ois.close();
    }

    public static void output(String path, Object obj) throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
        oos.writeObject(obj);
        oos.flush();
        oos.close();
    }
}

执行结果

**********************序列化 Duaghter**********************
**********************反序列化 Duaghter**********************
[hello, world]

实际上ArrayList的状态是被序列化和反序列化的,也就是说elementData被序列化了。 这就是如上源码中所示的两个方法:writeObject和readObject的功能。

我们可以有如下结论:在序列化过程中,如果被序列化的类中定义了writeObjectreadObject方法,虚拟机会试图调用对象类里的writeObjectreadObject方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,则默认调用是java.io.ObjectOutputStreamdefaultWriteObject方法以及java.io.ObjectInputStreamdefaultReadObject方法。用户自定义的writeObjectreadObject方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

优势

自己序列化可以过滤数组中无效的元素,只序列化数组中有效的元素,从而在进行I/O时能够提高读写效率,在内存中可以节省内容空间,从而提高性能。

(四)Java序列化与反序列化过程中类继承体系中构造函数调用问题

在序列化与反序列化过程中,如果祖先类实现了序列化接口(Serializable),那么从实现该接口的那个祖先类以后的所有子类(包括该类本身)都可以进行序列化操作。

序列化中子类和父类的构造方法调用问题:在进行序列化操作时,类继承体系中所有可实例化父类的相应构造函数自顶向下依次被调用;在进行反序列化时,如果类继承体系中某个父类(A)实现类序列化接口(Serializable),那么自可实例化根父类(Object)到父类A(不包括A类)的相应构造函数依次被调用,自父类A到该类为止的所有类的任何构造函数都不会被调用(最起码,从显示结果来看)。示例代码以及执行结果如下:

public class ObjectSerializableTest {
    public static void main(String[] args) throws Exception {
        String path = "res\\ost.dat";
        System.out.println("**********************序列化 Duaghter**********************");
        output(path, new Daughter());
        System.out.println("**********************反序列化 Duaghter**********************");
        input(path);

        System.out.println("**********************序列化 Son**********************");
        output(path, new Son());
        System.out.println("**********************反序列化 Son**********************");
        input(path);

        System.out.println("**********************序列化 Duaghter**********************");
        output(path, new User("Bruce", 23, "男"));
        System.out.println("**********************反序列化 Duaghter**********************");
        input(path);

        /**
         * 输出结果:
         * **********************序列化 Duaghter**********************
         *  SuperParent .. 
         *  GrandMother ..
         *  Mother ..
         *  Daughter ..
         * **********************反序列化 Duaghter**********************
         *  SuperParent .. 
         *  GrandMother ..
         *  Mother ..
         *  io.Daughter@42a57993
         * **********************序列化 Son**********************
         *  SuperParent .. 
         *  GrandFather ..
         *  Father ...
         *  Son ..
         * **********************反序列化 Son**********************
         *  SuperParent .. 
         *  GrandFather ..
         *  io.Son@74a14482
         *
         */
    }

    public static void input(String path) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
        Object obj =  ois.readObject();
        System.out.println(obj);
        ois.close();
    }

    public static void output(String path, Object obj) throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
        oos.writeObject(obj);
        oos.flush();
        oos.close();
    }
}

class SuperParent{
    SuperParent(){
        System.out.println("SuperParent .. ");
    }
}

class GrandMother extends SuperParent{
    GrandMother(){
        System.out.println("GrandMother ..");
    }
}

class GrandFather extends SuperParent{
    GrandFather(){
        System.out.println("GrandFather ..");
    }
}

class Father extends GrandFather implements Serializable{
    Father(){
        System.out.println("Father ...");
    }
}

class Mother extends GrandMother{
    Mother(){
        System.out.println("Mother ..");
    }
}

class Son extends Father{
    Son(){
        System.out.println("Son ..");
    }
}

class Daughter extends Mother implements Serializable{
    Daughter(){
        System.out.println("Daughter ..");
    }
}


以上的总结,仅仅是自己的当前学习理解的总结,难免有不足和错误,以后如果发现则会标记改正,如果希望更加详细的信息,可以参考如下的链接博文。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值