面试官:“聊聊Java序列化”

前言

java 的序列化大家肯定并不陌生, 在使用一些开源开源框架比如 dubbo 的时候,肯定踩过实体类没有实现序列化接口(java.io.Serializable)而报错的情况, 那大家有没有想过为什么要序列化实体类?如果实体类引用了一个不能序列化的类该怎么做呢?下面就给大家讲下我所探索的Java序列化以及他的使用场景。

面试官:“聊聊Java序列化”

如何序列化

  1. 首先实体类要实现 Serializable 接口
public class Student implements java.io.Serializable {
    private String name;
    private int age;
    // getter setter
    ...
}
  1. 然后可以使用 ObjectOutStream 序列化到本地文件
// 创建输出流
ObjectOutStream out = new ObjectOutputStream(new FileOutputStream("student.dat"))
// 创建需要序列化的对象    
Student jack = new Student("Jack", 21);
Student jim = new Student("Jim", 20);
// 写入流
out.writeObject(jack);
out.writeObject(jim);

如何反序列化

// 读回对象数据
ObjectInputStream in = new ObjectInputStream(new FileInputStream("student.dat"));
// 然后用 readObject方法以这些对象写入的顺序获得他们
Student jack = (Student) in.readObject();
Student jim = (Student) in.readObject();

方法调用顺序

注意:序列化write顺序和反序列化read顺序要一致

例如:

writeInt(a), writeInt(b)

readInt(a), readInt(b)

面试官:“聊聊Java序列化”

引用不能序列化的属性该怎么做?

很简单有几种方式

  1. 属性上面使用java 关键字 transient
  2. 方法上面用注解 @Transient
  3. 如果条件允许还可以改为静态属性

因为静态数据不在堆内存中,而是在静态方法区中

面试官:“聊聊Java序列化”

完整的例子

我们看下我们很常用的java ArrayList 的源码是怎么完成序列化的

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;
    
    ···
    
    /**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @serialData The length of the array backing the <tt>ArrayList</tt>
     *             instance is emitted (int), followed by all of its elements
     *             (each an <tt>Object</tt>) in the proper order.
     */
    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();
        }
    }

    /**
     * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
     * deserialize it).
     */
    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();
            }
        }
    }
  1. 他实现了 java.io.Serializable
  2. 他在字段 elementData 数组上面用了 transient 关键字
  3. 他还写了writeObject 和 readObject 方法

你如果用IDE查询的话发现这两个方法没实现任何接口,那是再什么时候调用的呢? 经过一番查阅资料发现 java.io.Serializable 的注释里面有写道

Classes that require special handling during the serialization and
deserialization process must implement special methods with these exact
signatures:
<PRE>
private void writeObject(java.io.ObjectOutputStream out)
    throws IOException
private void readObject(java.io.ObjectInputStream in)
    throws IOException, ClassNotFoundException;
private void readObjectNoData()
    throws ObjectStreamException;
</PRE>
···
@author  unascribed
@see java.io.ObjectOutputStream
@see java.io.ObjectInputStream
@see java.io.ObjectOutput
@see java.io.ObjectInput
@see java.io.Externalizable
@since   JDK1.1

也就是说这两个方法是特殊的回调方法, 当你的实体类很特殊需要手动序列化的时候就可以手动实现这两个方法

然后你可以返回去细品 ArrayList 是把 elementData 数组循环的writeObject 了

面试官:“聊聊Java序列化”

手动序列化和反序列化对象

Serializable 接口支持的钩子方法

先看下jdk序列化接口的定义

Classes that require special handling during the serialization and
deserialization process must implement special methods with these exact
signatures:
<PRE>
private void writeObject(java.io.ObjectOutputStream out)
    throws IOException
private void readObject(java.io.ObjectInputStream in)
    throws IOException, ClassNotFoundException;
private void readObjectNoData()
    throws ObjectStreamException;
</PRE>
···
@author  unascribed
@see java.io.ObjectOutputStream
@see java.io.ObjectInputStream
@see java.io.ObjectOutput
@see java.io.ObjectInput
@see java.io.Externalizable
@since   JDK1.1

就是告诉我们可以编写writeObject 和 readObject 来手动序列化(不受关键字修饰的影响)

序列化的使用场景

  1. 比如常见的Java对象的网络传输
  2. java小游戏开发存放游戏数据的时候比xml更方便存储复杂的关系

他是一种存储方式,只要你需要你就可以去用

总结

  1. 序列化使用 java.io.Serializable 接口
  2. 静态字段不会被序列化
  3. 字段屏蔽用 transient 关键字
  4. 自定义序列化编写 readObject 和 writeObject 方法
  5. 写入顺序和读取顺序要一致,就算不用也需要read一下
上一篇:利用python爬虫技术模拟提交问卷星/问卷网表单


下一篇:PHP-正确的PrettyPhoto rel属性?