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

前言

java 的序列化大家肯定並不陌生, 在使用一些開源開源框架比如

dubbo 的時候,肯定踩過實體類沒有實現序列化接口(java.io.Serializable)而報錯的情況, 那大家有沒有想過為什麼要序列化實體類?如果實體類引用了一個不能序列化的類該怎麼做呢?下面就給大家講下我所探索的Java序列化以及他的使用場景。


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

如何序列化

  1. 首先實體類要實現 Serializable 接口
<code>public class Student implements java.io.Serializable {
private String name;
private int age;
// getter setter
...
}/<code>
  1. 然後可以使用 ObjectOutStream 序列化到本地文件
<code>// 創建輸出流
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);/<code>

如何反序列化

<code>// 讀回對象數據
ObjectInputStream in = new ObjectInputStream(new FileInputStream("student.dat"));
// 然後用 readObject方法以這些對象寫入的順序獲得他們
Student jack = (Student) in.readObject();
Student jim = (Student) in.readObject();/<code>

方法調用順序

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

例如:

writeInt(a), writeInt(b)

readInt(a), readInt(b)

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

引用不能序列化的屬性該怎麼做?

很簡單有幾種方式

  1. 屬性上面使用java 關鍵字 transient
  2. 方法上面用註解 @Transient
  3. 如果條件允許還可以改為靜態屬性

因為靜態數據不在堆內存中,而是在靜態方法區中


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


完整的例子

我們看下我們很常用的java ArrayList 的源碼是怎麼完成序列化的


<code>public class ArrayList extends AbstractList
implements List, 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 ArrayList instance to a stream (that
* is, serialize it).
*
* @serialData The length of the array backing the ArrayList
* instance is emitted (int), followed by all of its elements
* (each an Object
) 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> s.writeObject(elementData[i]);
}

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

/**
* Reconstitute the ArrayList 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> a[i] = s.readObject();
}
}
}/<size>/<size>
/<code>
  1. 他實現了 java.io.Serializable
  2. 他在字段 elementData 數組上面用了 transient 關鍵字
  3. 他還寫了writeObject 和 readObject 方法

你如果用IDE查詢的話發現這兩個方法沒實現任何接口,那是再什麼時候調用的呢? 經過一番查閱資料發現 java.io.Serializable 的註釋裡面有寫道

<code>Classes that require special handling during the serialization and
deserialization process must implement special methods with these exact
signatures:

private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
private void readObjectNoData()
throws ObjectStreamException;

···
@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/<code>

也就是說這兩個方法是特殊的回調方法, 當你的實體類很特殊需要手動序列化的時候就可以手動實現這兩個方法

然後你可以返回去細品 ArrayList 是把

elementData 數組循環的writeObject 了


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


手動序列化和反序列化對象

Serializable 接口支持的鉤子方法

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

<code>Classes that require special handling during the serialization and
deserialization process must implement special methods with these exact

signatures:

private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
private void readObjectNoData()
throws ObjectStreamException;

···
@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/<code>

就是告訴我們可以編寫writeObject 和 readObject 來手動序列化(不受關鍵字修飾的影響)

序列化的使用場景

  1. 比如常見的Java對象的網絡傳輸
  2. java小遊戲開發存放遊戲數據的時候比xml更方便存儲複雜的關係

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

總結

  1. 序列化使用 java.io.Serializable 接口
  2. 靜態字段不會被序列化
  3. 字段屏蔽用 transient 關鍵字
  4. 自定義序列化編寫 readObject 和 writeObject 方法
  5. 寫入順序和讀取順序要一致,就算不用也需要read一下


分享到:


相關文章: