目錄
- 1 前言
- 2 正文
- 2.1 入門
- 2.2 流程分析
- 2.2.1 序列化流程分析
- 創建 ObjectOutputStream 對象,寫入流的頭信息
- writeObject(Object obj) 方法
- writeObject0(Object obj, boolean unshared) 方法
- writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) 方法
- writeSerialData(Object obj, ObjectStreamClass desc) 方法
- defaultWriteFields(Object obj, ObjectStreamClass desc) 方法
- 2.2.2 反序列化流程分析
- 2.3 實際開發中使用 `Serializable` 接口會遇到的問題
- 類實現了序列化接口,但是存在沒有實現序列化接口的成員,運行報錯:java.io.NotSerializableException
- 靜態變量爲什麼無法序列化?
- 多引用寫入問題:同一個引用,多次寫入不同的對象內容,但取出的對象是一模一樣的
- 父類實現了Serializable,子類沒有, 子類是否可以進行序列化?
- 子類實現序列化,父類不實現序列化,如何序列化父類的數據?
- 序列化的時候多一個字段,反序列化的時候少一個字段,或者序列化的時候少一個字段,反序列化的時候多一個字段,會不會報錯?
- writeReplace,writeObject, readResolve,readObject 的執行順序
- 反序列化打破單例,如何解決?
- 3 最後
- 參考
1 前言
本文會通過簡單的例子介紹如何對實現了 Serializable
接口的類進行序列化和反序列化,這部分是使用 Serializable
的入門;接着會重點分析序列化步驟與反序列化步驟,這部分會分析源碼,加深對原理的理解;最後會列舉實際開發中使用 Serializable
接口會遇到的問題並一一進行解決,這部分對開發中會遇到的問題進行填坑。
2 正文
2.1 入門
在實際開發中,我們會遇到這樣的需求:爲了將數據持久化,將對象轉化爲字節序列保存在磁盤上,或者反過來,需要使用數據時將保存在磁盤上的文件轉爲對象。前者稱爲序列化,後者稱爲反序列化。
會不會有同學這樣想,爲什麼不直接把對象存在磁盤上,而非要把對象轉爲字節序列呢?
這是因爲在系統底層,數據的傳輸形式是以簡單的字節序列形式傳遞,也就是說,在系統底層,不能識別對象,只能識別字節序列。
在 Java 中,需要類實現 Serializable
標記接口,並藉助 ObjectOutputStream
和 ObjectInputStream
實現序列化與反序列化。
這裏把序列化與反序列化的過程封裝爲工具類 SerializeUtils
,代碼如下:
public class SerializeUtils {
public static void writeObject(String filePath, Object obj) throws IOException {
FileOutputStream fos = new FileOutputStream(filePath);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
}
public static <T> T readObject(String filePath) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(filePath);
ObjectInputStream ois = new ObjectInputStream(fis);
T result = (T) ois.readObject();
ois.close();
return result;
}
}
對 SerializeUtils
進行簡單的說明:
writeObject(String filePath, Object obj)
方法接收兩個參數,第一個參數是要寫入的文件路徑,第二個參數是需要序列化的對象。在方法體內,首先創建了一個FileOutputStream
對象,文件輸出流;再把FileOutputStream
對象傳遞給ObjectOutputStream
的構造器,創建ObjectOutputStream
對象;接着,調用oos.writeObject(obj);
把對象寫入到文件中;最後關閉輸出流。readObject(String filePath)
方法接收一個參數,表示從哪個文件讀入。另外,這是一個泛型方法,方便在方法體內進行強制類型轉換。在方法體內,首先創建了一個FileInputStream
對象,文件輸入流;再把FileInputStream
對象傳遞給ObjectInputStream
的構造器,創建ObjectInputStream
對象;接着,調用ois.readObject()
獲取文件中的對象,並強轉爲替換了泛型類型參數的實際類型。不過,這裏的泛型方法在調用時可以利用類型推斷,免去了傳遞的類型來替換泛型類型參數的麻煩。
下面開始代碼演示:
Person1
類如下:
public class Person1 {
private String name;
private int age;
public Person1(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;
}
}
這是一個簡單的 Java Bean 類,包含了兩個字段,一個構造方法,還有 getter/setter 方法。
聲明文件路徑爲靜態變量:
private static String filePath = "./obj.object";
在 main()
方法中,開始序列化與反序列化:
SerializeUtils.writeObject(filePath, new Person1("wzc", 32));
Person1 person1 = SerializeUtils.<Person1>readObject(filePath);
System.out.println(person1.getName() + ":" + person1.getAge());
運行後,查看結果:
Exception in thread "main" java.io.NotSerializableException: com.java.advanced.features.io.serialize.serializable.Person1
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.java.advanced.features.io.serialize.SerializeUtils.writeObject(SerializeUtils.java:9)
at com.java.advanced.features.io.serialize.serializable.SerializableTest.test1(SerializableTest.java:301)
at com.java.advanced.features.io.serialize.serializable.SerializableTest.main(SerializableTest.java:13)
可以看到程序拋出了異常:NotSerializableException
,這是因爲 Person1
類沒有實現 Serializable
接口導致的。
創建 Person2
類,就是在 Person1
的基礎上實現 Serializable
接口。
public class Person2 implements Serializable {
// 省略了與 Person1 類似的代碼
}
再次進行測試,先執行序列化的代碼:
SerializeUtils.writeObject(filePath, new Person2("wzc", 32));
可以看到項目的根目錄會生成 obj.object 文件:
如果我們使用文本編輯器打開 obj.object 文件,可以看到文件裏會有一些亂碼:
\AC\ED\00sr\00<com.java.advanced.features.io.serialize.serializable.Person2\00\00\00\00\00\00\00\00I\00ageL\00namet\00Ljava/lang/String;xp\00\00\00 t\00\00\00\00\00
這是因爲編碼問題導致的,我們寫入到 obj.object 文件裏的是字節序列,而打開文本編輯器使用的解碼格式是 UTF-8 或者其它,如果字節序列在解碼格式的編碼表中找不到對應的字符,就會出現亂碼。
我們應該使用打開二進制文件的編輯器來查看。在 Windows 下,可以使⽤ NotePad++打開, 添加 Hex Editor 插件查看對應的⼆進制⽂件。這裏我使用的是 Ubuntu 的 GHex 工具來打開:
再執行反序列化的代碼:
Person2 person2 = SerializeUtils.readObject(filePath);
System.out.println(person2.getName() + ":" + person2.getAge());
運行後,打印結果如下:
wzc:32
到這裏,對如何使用對實現了 Serializable
接口的類進行序列化和反序列化已經介紹完畢。
下面開始分析序列化流程和反序列化流程:
2.2 流程分析
2.2.1 序列化流程分析
創建 ObjectOutputStream 對象,寫入流的頭信息
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
bout = new BlockDataOutputStream(out);
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
enableOverride = false;
// 寫入流的頭信息
writeStreamHeader();
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
我們看一下 writeStreamHeader()
方法的實現:
protected void writeStreamHeader() throws IOException {
bout.writeShort(STREAM_MAGIC);
bout.writeShort(STREAM_VERSION);
}
而 STREAM_MAGIC
和 STREAM_VERSION
是 ObjectOutputStream
所實現的接口 ObjectStreamConstants
中的常量:
public interface ObjectStreamConstants {
/**
* Magic number that is written to the stream header.
*/
final static short STREAM_MAGIC = (short)0xaced;
/**
* Version number that is written to the stream header.
*/
final static short STREAM_VERSION = 5;
// 省略了其他常量。。。
}
再來看一下二進制文件的截圖,可以對應到寫入的信息:
writeObject(Object obj) 方法
public final void writeObject(Object obj) throws IOException {
if (enableOverride) { // enableOverride 是 false,不會走這個分支的
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false); // => 代碼走到這裏
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
writeObject0(Object obj, boolean unshared) 方法
writeObject0()
方法是 writeObject()
方法的底層實現。
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
boolean oldMode = bout.setBlockDataMode(false);
try {
// handle previously written and non-replaceable objects
// 省略與分析無關的代碼
Class<?> cl = obj.getClass();
ObjectStreamClass desc = ObjectStreamClass.lookup(cl, true);
// 省略與分析無關的代碼
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared); // => 代碼會走這裏
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
bout.setBlockDataMode(oldMode);
}
}
這個方法裏面的
Class<?> cl = obj.getClass();
ObjectStreamClass.lookup(cl, true)
lookup()
方法會去查找並返回給定類的類描述符對象,即 ObjectStreamClass
對象。
lookup()
方法的實現思路就是先查找緩存有沒有 ObjectStreamClass
對象,有則返回;沒有的話,就去調用 ObjectStreamClass
的構造方法創建 ObjectStreamClass
對象。
我們不用去考慮緩存,因爲我們的代碼剛跑起來,哪有緩存?
我們直接去看 ObjectStreamClass
的構造方法:
private ObjectStreamClass(final Class<?> cl) {
this.cl = cl;
name = cl.getName(); // 類名
isProxy = Proxy.isProxyClass(cl); // 是否是代理類
isEnum = Enum.class.isAssignableFrom(cl); // 是否是枚舉類
serializable = Serializable.class.isAssignableFrom(cl); // 是否實現了 Serializable 接口
externalizable = Externalizable.class.isAssignableFrom(cl); // 是否實現了 Externalizable 接口
Class<?> superCl = cl.getSuperclass();
superDesc = (superCl != null) ? lookup(superCl, false) : null;
localDesc = this;
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (isEnum) {
suid = Long.valueOf(0);
fields = NO_FIELDS;
return null;
}
if (cl.isArray()) {
fields = NO_FIELDS;
return null;
}
suid = getDeclaredSUID(cl); // 獲取 serialVersionUID 的值
try {
fields = getSerialFields(cl);
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
new ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
// 獲取 private void writeObject(ObjectOutputStream oos) 方法的 Method 對象
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
// 獲取 private void readObject(ObjectInputStream ois) 方法的 Method 對象
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
domains = getProtectionDomains(cons, cl);
// 獲取 private Object writeReplace() 方法的 Method 對象
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
// 獲取 private Object readResolve() 方法的 Method 對象
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
} else {
suid = Long.valueOf(0);
fields = NO_FIELDS;
}
try {
fieldRefl = getReflector(fields, this);
} catch (InvalidClassException ex) {
// field mismatches impossible when matching local fields vs. self
throw new InternalError(ex);
}
if (deserializeEx == null) {
if (isEnum) {
deserializeEx = new ExceptionInfo(name, "enum type");
} else if (cons == null) {
deserializeEx = new ExceptionInfo(name, "no valid constructor");
}
}
for (int i = 0; i < fields.length; i++) {
if (fields[i].getField() == null) {
defaultSerializeEx = new ExceptionInfo(
name, "unmatched serializable field(s) declared");
}
}
initialized = true;
}
從上面的註釋可以看到,ObjectStreamClass
類就是在序列化過程中要來描述需要序列化的對象的。
回到 writeObject0()
方法裏,obj
就是 Person2
對象,它實現了 Serializable
接口,所以obj instanceof Serializable
爲 true
,代碼會走 writeOrdinaryObject(obj, desc, unshared)
方法。
writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) 方法
這個方法的參數值爲:
Object obj
, 即Person2
對象;ObjectStreamClass desc
,即對應於Person2
類的類描述信息封裝;boolean unshared
,即false
。
writeOrdinaryObject()
方法的含義是把普通的可序列化對象寫入流中。普通的含義是除了 String
,ObjectStreamClass
,ObjectStreamClass
,數組,枚舉常量之外的類對象。
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
try {
desc.checkSerialize();
// 寫入表示一個新的對象的字節,final static byte TC_OBJECT = (byte)0x73;
bout.writeByte(TC_OBJECT);
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
writeClassDesc(desc, false);
方法表示把類的描述信息 ObjectStreamClass
寫入流中,這些信息包括表示類描述的信息,類的全類名信息,實現 Serializable
或 Externalizable
的信息,字段的個數,字段的類型碼,字段的名稱,非基本類型字段的類型信息。
需要注意的是,writeClassDesc(desc, false)
寫入的是類的信息,並不包括對象的信息,即字段的值。
這裏不再詳述了。
Person2
沒有實現 Externalizable
接口,所以 desc.isExternalizable()
爲 false
,代碼進入 else
分支:writeSerialData(obj, desc);
writeSerialData(Object obj, ObjectStreamClass desc) 方法
這個方法的參數值爲:
Object obj
, 即Person2
對象;ObjectStreamClass desc
,即對應於Person2
類的類描述信息封裝;
writeSerialData()
方法的作用是寫入序列化數據,即給定對象的實例化數據,也包括超類的實例化數據。
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) { // 返回 false
// 省略無關代碼
} else {
defaultWriteFields(obj, slotDesc); // => 代碼走這裏
}
}
}
defaultWriteFields(Object obj, ObjectStreamClass desc) 方法
這個方法的參數值爲:
Object obj
, 即Person2
對象;ObjectStreamClass desc
,即對應於Person2
類的類描述信息封裝;
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
}
desc.checkDefaultSerialize();
// 把基本類型的字段值,放在 primVals 字節數組裏
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
desc.getPrimFieldValues(obj, primVals);
// 寫入基本類型的字段值
bout.write(primVals, 0, primDataSize, false);
// 再次調用 writeObject0() 方法寫入非基本類型的字段值。
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
for (int i = 0; i < objVals.length; i++) {
try {
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
2.2.2 反序列化流程分析
創建 ObjectInputStream 對象,檢查頭信息
public ObjectInputStream(InputStream in) throws IOException {
verifySubclass();
bin = new BlockDataInputStream(in);
handles = new HandleTable(10);
vlist = new ValidationList();
serialFilter = ObjectInputFilter.Config.getSerialFilter();
enableOverride = false;
readStreamHeader();
bin.setBlockDataMode(true);
}
readStreamHeader();
會檢查頭信息,查看代碼:
protected void readStreamHeader()
throws IOException, StreamCorruptedException
{
short s0 = bin.readShort();
short s1 = bin.readShort();
if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) {
throw new StreamCorruptedException(
String.format("invalid stream header: %04X%04X", s0, s1));
}
}
讀取頭兩個 short
值,如果第一個 short
值不等於 STREAM_MAGIC
或第二個short
值不等於STREAM_VERSION
,那麼就拋出異常:StreamCorruptedException
,表示頭信息無效。
Object readObject() 方法
public final Object readObject()
throws IOException, ClassNotFoundException {
return readObject(Object.class);
}
Object readObject(Class<?> type) 方法
參數的值:
Class<?> type
:Object.class
private final Object readObject(Class<?> type)
throws IOException, ClassNotFoundException
{
if (enableOverride) { // false
return readObjectOverride();
}
if (! (type == Object.class || type == String.class))
throw new AssertionError("internal error");
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(type, false); // => 代碼會走這裏
// 省略無關的代碼
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
Object readObject0(Class<?> type, boolean unshared) 方法
private Object readObject0(Class<?> type, boolean unshared) throws IOException {
byte tc;
// bin.peekByte() 返回流中的字節,讀取到的是 TC_OBJECT
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}
depth++;
totalObjectRefs++;
try {
switch (tc) {
case TC_OBJECT:
if (type == String.class) {
throw new ClassCastException("Cannot cast an object to java.lang.String");
}
return checkResolve(readOrdinaryObject(unshared));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
Object readOrdinaryObject(boolean unshared) 方法
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
// 從流中讀取類描述信息 ObjectStreamClass 對象
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
// 實例化對象,即 Person2 對象
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
passHandle = handles.assign(unshared ? unsharedMarker : obj);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(passHandle, resolveEx);
}
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc); // => 代碼走到這裏
}
handles.finish(passHandle);
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
readSerialData(Object obj, ObjectStreamClass desc) 方法
這個方法的作用是給實例化的對象字段賦值。
private void readSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slots[i].hasData) {
if (obj == null || handles.lookupException(passHandle) != null) {
defaultReadFields(null, slotDesc); // skip field values
} else if (slotDesc.hasReadObjectMethod()) {
// 省略無關的代碼
} else {
defaultReadFields(obj, slotDesc); // => 代碼走這裏
}
if (slotDesc.hasWriteObjectData()) {
skipCustomData();
} else {
bin.setBlockDataMode(false);
}
}
}
}
defaultReadFields(Object obj, ObjectStreamClass desc) 方法
private void defaultReadFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
}
// 先設置基本類型字段的值
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
bin.readFully(primVals, 0, primDataSize, false);
if (obj != null) {
desc.setPrimFieldValues(obj, primVals);
}
// 再調用 readObject0 設置非基本類型字段的值。
int objHandle = passHandle;
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
for (int i = 0; i < objVals.length; i++) {
ObjectStreamField f = fields[numPrimFields + i];
objVals[i] = readObject0(Object.class, f.isUnshared());
if (f.getField() != null) {
handles.markDependency(objHandle, passHandle);
}
}
if (obj != null) {
desc.setObjFieldValues(obj, objVals);
}
passHandle = objHandle;
}
2.3 實際開發中使用 Serializable
接口會遇到的問題
類實現了序列化接口,但是存在沒有實現序列化接口的成員,運行報錯:java.io.NotSerializableException
需要使用 transient
關鍵字修飾沒有實現序列化接口的成員。
靜態變量爲什麼無法序列化?
靜態變量不參與序列化,序列化的是對象的成員字段。
多引用寫入問題:同一個引用,多次寫入不同的對象內容,但取出的對象是一模一樣的
演示代碼:
// 序列化
FileOutputStream fos = new FileOutputStream(filePath);
ObjectOutputStream oos = new ObjectOutputStream(fos);
Person2 personWrite = new Person2("wzc", 32);
oos.writeObject(personWrite);
personWrite.setAge(33);
oos.writeObject(personWrite);
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream(filePath);
ObjectInputStream ois = new ObjectInputStream(fis);
Person2 personRead1 = (Person2) ois.readObject();
Person2 personRead2 = (Person2) ois.readObject();
ois.close();
System.out.println("personWrite:" + personWrite);
System.out.println("personRead1:" + personRead1);
System.out.println("personRead2:" + personRead2);
System.out.println("personRead1 == personRead2:" + (personRead1 == personRead2));
打印信息:
personWrite:Person2@692404036{name='wzc', age=33}
personRead1:Person2@1072408673{name='wzc', age=32}
personRead2:Person2@1072408673{name='wzc', age=32}
personRead1 == personRead2:true
第一次使用 personWrite
寫入的對象內容是 “wzc”, 32;
第二次使用 personWrite
寫入的對象內容是 “wzc”, 33;
但是,反序列化讀取到的是一模一樣的對象。
解決辦法:
- 在第二次寫入之前增加代碼
oos.reset();
- 把第二次寫入的代碼:
oos.writeObject(personWrite);
替換爲oos.writeUnshared(personWrite);
- 儘量避免多引用寫入,使用不同的引用。
父類實現了Serializable,子類沒有, 子類是否可以進行序列化?
可以。
子類實現序列化,父類不實現序列化,如何序列化父類的數據?
首先,要給父類添加空參構造器,否則會報錯:java.io.InvalidClassException: com.java.advanced.features.io.serialize.serializable.Man; no valid constructor;
其次,讓子類負責序列化(反序列化)父類的域。
代碼如下:
public class Person7 {
public String name;
public int age;
// 添加了無參構造器
public Person7() {
}
public Person7(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Man3 extends Person7 implements Serializable {
public double salary;
public Man3(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
private void writeObject(ObjectOutputStream oos) throws IOException {
// 先序列化本類對象
oos.defaultWriteObject();
// 再序列化父類的域
oos.writeObject(name);
oos.writeInt(age);
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// 先反序列化本類對象
ois.defaultReadObject();;
// 再反序列化父類的域
name = (String) ois.readObject();
age = ois.readInt();
}
}
序列化的時候多一個字段,反序列化的時候少一個字段,或者序列化的時候少一個字段,反序列化的時候多一個字段,會不會報錯?
需要顯式地聲明 serialVersionUID
的值,如爲 1L。
private static final long serialVersionUID = 1L;
因爲計算默認的 serialVersionUID
對類的詳細信息具有較高的敏感性,根據編譯器實現的不同可能千差萬別,這樣在反序列化過程中可能會導致意外的 InvalidClassException
。
如果類的 serialVersionUID
是一致的,即便在序列化時的類和反序列化時的類有些不同,該對象仍會被盡最大限度完成反序列化。
writeReplace,writeObject, readResolve,readObject 的執行順序
writeReplace 先於writeObject, readResolve後於readObject
反序列化打破單例,如何解決?
給單例添加 readResovle()
方法:
public class SingletonSerializeFix implements Serializable {
private static final long serialVersionUID = 1L;
private SingletonSerializeFix() {
//no instance
}
public static SingletonSerializeFix getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static SingletonSerializeFix instance = new SingletonSerializeFix();
}
private Object readResolve() {
return SingletonHolder.instance;
}
}
3 最後
2.3 部分只是說明了問題以及結論,沒有進行詳細地說明,代碼都有演示在 github 地址裏。
代碼地址:Github地址。
希望這篇文章,能夠加深大家對 Serializable
的學習,有助於實際的開發工作。