一、引言
java.io.ObjectOutputStream是實現序列化的關鍵類,它可以將一個對象轉換成二進制流,然後可以通過ObjectInputStream將二進制流還原成對象。
在閱讀ObjectOutputStream源碼之前,我們先來回顧一下序列化相關的基礎知識:
1、需要序列化的類必須實現java.io.Serializable接口,否則會拋出NotSerializableException異常
2、如果檢測到反序列化後的類的serialVersionUID和對象二進制流的serialVersionUID不同,則會拋出
異常。
3、Java的序列化會將一個類包含的引用中所有的成員變量保存下來(深度複製),所以裏面的引用類型必須也要實現java.io.Serializable接口。
4、對於不採用默認序列化或無須序列化的成員變量,可以添加transient關鍵字,並不是說添加了transient關鍵字就一定不能序列化。
5、每個類可以實現readObject、writeObject方法實現自己的序列化策略,即使是transient修飾的成員變量也可以手動調用ObjectOutputStream的writeInt等方法將這個成員變量序列化。
二、使用方法
我們先來回顧一下ObjectOutputStream的使用方法:
class Person implements Serializable {
private static final long serialVersionUID = 1386583756403881124L;
String name;
int age;
}
public class Test {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("D:\\out.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Person p = new Person();
p.name = "LZF";
p.age = 19;
oos.writeObject(p);
oos.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
ObjectOutputStream只有一個public權限的構造方法:該構造方法需要傳入一個OutputStream表示將對象二進制流寫入到指定的OutputStream。
查看out.txt:輸出數據如下:
三、源碼簡要分析
ObjectOutputStream的實現很複雜,建議讀者們先對ObjectOutputStream源碼的主要方法先過一遍再往下看。
ObjectOutputStream類定義:
public class ObjectOutputStream extends OutputStream
implements ObjectOutput, ObjectStreamConstants {
//...
}
- 1
- 2
- 3
- 4
ObjectOutputStream繼承了OutputStream類,實現了ObjectOutput接口和ObjectStreamConstants接口
ObjectStreamConstants接口並沒有定義方法,其內部定義了很多byte類型常量,表示序列化後的單個字節數據的含義。
瞭解完這些成員變量後,我們從幾個最常用的序列化操作爲切入點分析:ObjectOutputStream的構造方法和writeObject方法。
ObjectOutputStream的構造方法:
public ObjectOutputStream(OutputStream out) throws IOException {
//檢查繼承權限
verifySubclass();
//構造一個BlockDataOutputStream用於向out寫入序列化數據
bout = new BlockDataOutputStream(out);
//構造一個大小爲10,負載因子爲3的HandleTable和ReplaceTable
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
//恆爲false,除非子類調用protected構造方法
enableOverride = false;
writeStreamHeader();
//將緩存模式打開,寫入數據時先寫入緩衝區
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
BlockDataOutputStream是ObjectOutputStream的內部類,它將構造ObjectOutputStream傳入的OutputStream實例包裝起來,當外部類ObjectOutputStream需要向這個OutputStream寫入序列化數據時,就由這個類來完成實際的寫入操作。
構造方法首先調用verifySubclass方法分析現在構造的是不是ObjectOutputStream的子類,即:
private void verifySubclass() {
Class<?> cl = getClass();
//如果構造的不是ObjectOutputStream的子類則直接返回
if (cl == ObjectOutputStream.class)
return;
//否則獲取安全管理器檢查是否有繼承ObjectOutputStream的權限
SecurityManager sm = System.getSecurityManager();
if (sm == null)
return;
//移除Caches中已經失去引用的Class對象
processQueue(Caches.subclassAuditsQueue, Caches.subclassAudits);
//將ObjectOutputStream的子類存入Caches
WeakClassKey key = new WeakClassKey(cl, Caches.subclassAuditsQueue);
Boolean result = Caches.subclassAudits.get(key);
if (result == null) {
result = Boolean.valueOf(auditSubclass(cl));
Caches.subclassAudits.putIfAbsent(key, result);
}
if (result.booleanValue())
return;
//如果沒有權限則拋出SecurityException異常
sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
該方法如果識別到構造的是ObjectOutputStream的子類,則會檢查是否擁有SUBCLASS_IMPLEMENTATION_PERMISSION權限,否則拋出SecurityException異常。
另外,ObjectOutputStream通過一個Cache靜態內部類中的ConcurrentHashMap來緩存ObjectOutputStream子類信的息。Class類通過內部類WeakClassKey(繼承WeakReference,將一個弱引用指向一個Class對象)存儲。
private static class Caches {
static final ConcurrentMap<WeakClassKey,Boolean> subclassAudits = new ConcurrentHashMap<>();
static final ReferenceQueue<Class<?>> subclassAuditsQueue = new ReferenceQueue<>();
}
- 1
- 2
- 3
- 4
在進行完ObjectOutputStream的類型檢查後,構造方法會隨之構建一個BlockDataOutputStream用於向傳入的OutputStream寫入對象信息,並構建長度爲10,負載因子爲3的HandleTable和ReplaceTable。隨後,將魔數(0xACED)和版本標識符(0x0005)寫入文件頭,用來檢測是不是一個序列化對象。
protected void writeStreamHeader() throws IOException {
bout.writeShort(STREAM_MAGIC); //寫入兩個字節:0xAC和0xED
bout.writeShort(STREAM_VERSION); //寫入兩個字節:0x00和0x05
}
- 1
- 2
- 3
- 4
最後根據sun.io.serialization.extendedDebugInfo配置信息決定是否啓用調式信息棧。
private static final boolean extendedDebugInfo =
java.security.AccessController.doPrivileged(
new sun.security.action.GetBooleanAction(
"sun.io.serialization.extendedDebugInfo")).booleanValue();
- 1
- 2
- 3
- 4
如果extendedDebugInfo爲true,則構造方法會構造一個DebugTraceInfoStack,否則置爲null。
構造完ObjectOutputStream對象後,我們一般會隨之調用writeObject(Object)方法將對象寫入
public final void writeObject(Object obj) throws IOException {
//在ObjectOutputStream中這個變量恆爲false,只有子類爲true
if (enableOverride) {
//實現爲空,供子類重寫用
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0)
writeFatalException(ex);
throw ex;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
writeObject方法首先會檢查是否是ObjectOutputStream的子類,如果是則調用writeObjectOverride方法,這個方法默認實現爲空,需要子類根據實際業務需求定製序列化方法。
隨後調用writeObject0方法
private void writeObject0(Object obj, boolean unshared) throws IOException {
//關閉緩衝模式,直接向目標OutputStream寫入數據
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
int h;
//處理以前寫過的和不可替換的對象
//如果obj爲null(只有當obj爲null時纔會返回null)
if ((obj = subs.lookup(obj)) == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}
Object orig = obj;
Class<?> cl = obj.getClass();
//序列化對象對應的Class對象的詳細信息
ObjectStreamClass desc;
for (;;) {
Class<?> repCl;
//獲取序列化對象對應的Class對象詳細信息,待會會討論ObjectStreamClass
desc = ObjectStreamClass.lookup(cl, true);
//直接break,因爲最後(repCl=obj.getClass())==null恆等於true(我也不知道爲什麼這裏要有for循環)
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl)
break;
cl = repCl;
}
if (enableReplace) {
//replaceObject用來替換這個對象進行序列化,默認實現爲空,一般用於子類重寫實現序列化的定製
Object rep = replaceObject(obj);
//如果對象被替換了
if (rep != obj && rep != null) {
cl = rep.getClass();
//重新查找對應的ObjectStreamClass
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}
//如果對象被替換了(非ObjectOutputStream子類不會發生)
if (obj != orig) {
subs.assign(orig, obj);
if (obj == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}
}
//序列化對象類型爲String、數組、枚舉時,調用定製的寫入方法
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 {
//結束方法前將方法棧深減去1
depth--;
bout.setBlockDataMode(oldMode);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
ObjectStreamClass存儲了一個Class對象的信息,其實例變量包括:Class對象,Class名稱,serialVersionUID,實現了Serializable接口還是 Externalizable接口,非transient修飾的變量,自定義的writeObject和readObject的Method對象。
下面來看ObjectStreamClass的lookup方法:
static ObjectStreamClass lookup(Class<?> cl, boolean all) {
//如果all爲false且cl並沒有實現Serializable接口則直接返回null
if (!(all || Serializable.class.isAssignableFrom(cl))) {
return null;
}
//清除失去Class引用的ObjectStreamClass緩存
//(緩存的用途是避免反覆對同一個Class創建ObjectStreamClass對象)
processQueue(Caches.localDescsQueue, Caches.localDescs);
//創建一個臨時的WeakClassKey用於從緩存中查找對應的ObjectStreamClass或EntryFuture
WeakClassKey key = new WeakClassKey(cl, Caches.localDescsQueue);
//獲取保存有ObjectStreamClass或EntryFuture的引用
Reference<?> ref = Caches.localDescs.get(key);
Object entry = null;
//如果引用不爲null則直接獲取其中的對象給entry
if (ref != null) {
entry = ref.get();
}
EntryFuture future = null;
//如果引用的對象被GC
if (entry == null) {
//創建一個EntryFuture對象並將軟引用newRef指向它
EntryFuture newEntry = new EntryFuture();
Reference<?> newRef = new SoftReference<>(newEntry);
do {
//從緩存中刪除這個失去引用的鍵值對
if (ref != null)
Caches.localDescs.remove(key, ref);
//將被newRef引用的EntryFuture添加到緩存(這裏使用putIfAbsent而不是put可能是爲了防止有其它線程已經添加了)
ref = Caches.localDescs.putIfAbsent(key, newRef);
if (ref != null)
entry = ref.get();
//循環直到ref爲null或entry不爲null
} while (ref != null && entry == null);
//如果entry爲null
if (entry == null)
future = newEntry;
}
//如果從緩存中拿到了ObjectStreamClass
if (entry instanceof ObjectStreamClass) {
return (ObjectStreamClass) entry;
}
//如果從緩存中得到了EntryFuture
if (entry instanceof EntryFuture) {
future = (EntryFuture) entry;
//如果創建這個EntryFuture的線程就是當前線程,即這個EntryFuture
//是在前面的代碼ref = Caches.localDescs.putIfAbsent(key, newRef);語句中設置的
if (future.getOwner() == Thread.currentThread()) {
entry = null;
} else {
entry = future.get();
}
}
//如果entry爲null那麼就創建一個新的ObjectStreamClass對象並加入緩存
if (entry == null) {
try {
entry = new ObjectStreamClass(cl);
} catch (Throwable th) {
entry = th;
}
//設置這個ObjectStreamClass實例
if (future.set(entry)) {
Caches.localDescs.put(key, new SoftReference<Object>(entry));
} else {
entry = future.get();
}
}
//最後如果entry爲ObjectOutputStream則直接返回,否則拋出異常
if (entry instanceof ObjectStreamClass) {
return (ObjectStreamClass) entry;
} else if (entry instanceof RuntimeException) {
throw (RuntimeException) entry;
} else if (entry instanceof Error) {
throw (Error) entry;
} else {
throw new InternalError("unexpected entry: " + entry);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
在ObjectStreamClass類的內部類Caches中,存在一個類型爲ConcurrentMap的靜態成員變量localDescs:
static final ConcurrentMap<WeakClassKey,Reference<?>> localDescs = new ConcurrentHashMap<>();
private static final ReferenceQueue<Class<?>> localDescsQueue = new ReferenceQueue<>();
- 1
- 2
ObjectStreamClass引入這個緩存主要是爲了提高獲取類信息的速度,如果反覆對一個類的實例們進行序列化操作,那麼只需要實例化一個ObjectStreamClass實例並導入這個緩存。
WeakClassKey繼承WeakReference,將一個弱引用指向這個Class對象,當對應的ClassLoader失去引用時,不至於導致垃圾回收器無法回收這個Class對象。
引用隊列localDescsQueue主要用於processQueue方法清除localDescs中無用的緩存。
至於ObjectStreamClass的內部類EntryFuture的作用,我個人認爲是爲了實現多線程調用lookup方法而設立的。
private static class EntryFuture {
private static final Object unset = new Object();
private final Thread owner = Thread.currentThread();
private Object entry = unset;
//entry是ObjectStreamClass實例
synchronized boolean set(Object entry) {
if (this.entry != unset)
return false;
this.entry = entry;
//entry已被設置,喚醒正在調用get方法的線程
notifyAll();
return true;
}
synchronized Object get() {
boolean interrupted = false;
while (entry == unset) {
try {
//等待到entry被set爲止
wait();
} catch (InterruptedException ex) {
interrupted = true;
}
}
//如果被強制打斷則返回null
if (interrupted) {
AccessController.doPrivileged(
new PrivilegedAction<Void>() {
public Void run() {
Thread.currentThread().interrupt();
return null;
}
});
}
//如果是正常被set方法喚醒的則直接返回設置好的ObjectStreamClass
return entry;
}
//返回創建這個EntryFuture的線程
Thread getOwner() {
return owner;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
現在回到writeObject0方法中
在獲取到ObjectStreamClass對象後,會判斷需要序列化的類是哪種類型。
下面我們就只分析writeOrdinaryObject方法:
private void writeOrdinaryObject(Object obj, ObjectStreamClass desc,
boolean unshared) throws IOException {
if (extendedDebugInfo)
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class \"" +
obj.getClass().getName() + "\", " + obj.toString() + ")");
try {
//檢查ObjectStreamClass對象
desc.checkSerialize();
//寫入字節0x73
bout.writeByte(TC_OBJECT);
//寫入對應的Class對象的信息
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();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
writeOrdinaryObject最終會以一種遞歸的形式寫入對象信息。
writeSerialData方法會將這個實例及其父類基本數據類型寫入文件,如果檢測到有引用類型,那麼會繼續調用writeObject0方法寫入,直到將這個對象包含的所有信息全部序列化爲止。
暫時就只能分析到這裏了。