Java序列化機(jī)制詳解
Serializable 接口
Serializable 接口是 Java 提供的標(biāo)記接口,沒(méi)有包含任何需要實(shí)現(xiàn)的方法。實(shí)現(xiàn)了這個(gè)接口的類(lèi)表明其對(duì)象是可序列化的,可以被轉(zhuǎn)換為字節(jié)流。
public interface Serializable {
}
通過(guò)實(shí)現(xiàn) Serializable 接口,標(biāo)識(shí)類(lèi)的對(duì)象可以被序列化。這使得對(duì)象可以在網(wǎng)絡(luò)上傳輸或保存到文件中,而不失去其狀態(tài)和結(jié)構(gòu)。
序列化過(guò)程
序列化是將對(duì)象的狀態(tài)(字段值)轉(zhuǎn)換為字節(jié)流的過(guò)程。這個(gè)過(guò)程由 ObjectOutputStream 類(lèi)來(lái)完成。序列化使得對(duì)象可以以字節(jié)流的形式進(jìn)行存儲(chǔ)或傳輸,便于在不同系統(tǒng)之間進(jìn)行數(shù)據(jù)交換。如下我們列舉幾個(gè)重要的方法的源碼:
writeObject方法
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
enableOverride 表示是否啟用了對(duì)象寫(xiě)入的覆蓋機(jī)制。如果啟用,會(huì)調(diào)用 writeObjectOverride 方法來(lái)執(zhí)行對(duì)象的特定寫(xiě)入邏輯。
如果沒(méi)有啟用覆蓋機(jī)制,則調(diào)用 writeObject0 方法執(zhí)行實(shí)際的對(duì)象序列化。
writeObject0 方法負(fù)責(zé)處理對(duì)象的序列化,其中第二個(gè)參數(shù) false 表示不使用不共享的方式進(jìn)行序列化。
如果在序列化過(guò)程中拋出 IOException 異常,會(huì)捕獲該異常。如果當(dāng)前深度為0(表示不在嵌套序列化過(guò)程中),則調(diào)用 writeFatalException 方法來(lái)處理異常,否則將異常重新拋出。
writeObject0
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
// handle previously written and non-replaceable objects
int h;
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;
}
// check for replacement object
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
for (;;) {
// REMIND: skip this check for strings/arrays?
Class<?> repCl;
desc = ObjectStreamClass.lookup(cl, true);
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl)
{
break;
}
cl = repCl;
}
if (enableReplace) {
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}
// if object replaced, run through original checks a second time
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;
}
}
// 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 {
depth--;
bout.setBlockDataMode(oldMode);
}
}
反序列化過(guò)程
當(dāng)需要從字節(jié)流中恢復(fù)對(duì)象時(shí),Java 序列化機(jī)制會(huì)將字節(jié)流還原為對(duì)象的狀態(tài)。這個(gè)過(guò)程由 ObjectInputStream 類(lèi)來(lái)完成。如下我們列舉幾個(gè)重要的方法的源碼:
readObject()
private final Object readObject(Class<?> type)
throws IOException, ClassNotFoundException
{
if (enableOverride) {
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);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
這段代碼的主要作用是根據(jù)給定的類(lèi)型 (type) 進(jìn)行對(duì)象的反序列化。在這個(gè)過(guò)程中,它使用了一些狀態(tài)變量,如 enableOverride、passHandle、handles、depth、vlist 等,來(lái)管理反序列化的過(guò)程。在處理嵌套對(duì)象時(shí),它通過(guò) markDependency 方法標(biāo)記了當(dāng)前對(duì)象與封閉對(duì)象的依賴(lài)關(guān)系。在深度為 0 時(shí),執(zhí)行了clear方法。具體反序列化執(zhí)行的核心方法是readObject0()
readObject0()
private Object readObject0(Class<?> type, boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}
byte tc;
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}
depth++;
totalObjectRefs++;
try {
switch (tc) {
case TC_NULL:
return readNull();
case TC_REFERENCE:
// check the type of the existing object
return type.cast(readHandle(unshared));
case TC_CLASS:
if (type == String.class) {
throw new ClassCastException("Cannot cast a class to java.lang.String");
}
return readClass(unshared);
case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
if (type == String.class) {
throw new ClassCastException("Cannot cast a class to java.lang.String");
}
return readClassDesc(unshared);
case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));
case TC_ARRAY:
if (type == String.class) {
throw new ClassCastException("Cannot cast an array to java.lang.String");
}
return checkResolve(readArray(unshared));
case TC_ENUM:
if (type == String.class) {
throw new ClassCastException("Cannot cast an enum to java.lang.String");
}
return checkResolve(readEnum(unshared));
case TC_OBJECT:
if (type == String.class) {
throw new ClassCastException("Cannot cast an object to java.lang.String");
}
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
if (type == String.class) {
throw new ClassCastException("Cannot cast an exception to java.lang.String");
}
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}
case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
serialVersionUID
serialVersionUID 是用于版本控制的序列化版本號(hào)。它是一個(gè)長(zhǎng)整型數(shù)值,用于標(biāo)識(shí)類(lèi)的版本。通過(guò)顯式聲明 serialVersionUID,可以在類(lèi)結(jié)構(gòu)發(fā)生變化時(shí)依然能夠正確地進(jìn)行反序列化。
@Data
public class LoginUserInfo implements Serializable {
private static final long serialVersionUID = 1L;
...
}
如果在類(lèi)中沒(méi)有明確聲明 serialVersionUID,Java 運(yùn)行時(shí)系統(tǒng)會(huì)根據(jù)類(lèi)的結(jié)構(gòu)自動(dòng)生成一個(gè)。這種自動(dòng)生成的 serialVersionUID 是基于類(lèi)的各個(gè)方面的,包括字段、方法、父類(lèi)等。如果類(lèi)的結(jié)構(gòu)發(fā)生變化,可能導(dǎo)致自動(dòng)生成的 serialVersionUID 發(fā)生變化。這可能會(huì)導(dǎo)致在反序列化時(shí),類(lèi)的版本不一致,從而導(dǎo)致 InvalidClassException 異常。
所以顯式聲明 serialVersionUID是確保反序列化過(guò)程正確的關(guān)鍵,避免因類(lèi)結(jié)構(gòu)變化而導(dǎo)致的問(wèn)題。
transient 關(guān)鍵字
關(guān)鍵字 transient 用于標(biāo)記字段,表示在對(duì)象序列化的過(guò)程中,這個(gè)字段應(yīng)該被忽略。例如,如果一個(gè)類(lèi)有一個(gè)不希望被序列化的緩存字段,可以使用 transient 關(guān)鍵字來(lái)避免將其寫(xiě)入序列化數(shù)據(jù)。例如ArrayList、LinkedList 等類(lèi)中的一些屬性就是使用transient修飾的:

_20231218215928.jpg

_20231218215951.jpg
自定義序列化和反序列化
有時(shí)候,可能需要自定義序列化和反序列化的過(guò)程以滿(mǎn)足特定需求??梢酝ㄟ^(guò)實(shí)現(xiàn) writeObject 和 readObject 方法來(lái)實(shí)現(xiàn)自定義邏輯。如ArrayList類(lèi)中就是通過(guò)自定義的序列化和反序列化方法:
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
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, 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();
}
}
}
序列化不保存靜態(tài)變量
- 對(duì)象狀態(tài) vs. 類(lèi)狀態(tài)
序列化的主要目的是保存對(duì)象的狀態(tài),即對(duì)象的實(shí)例變量。靜態(tài)變量是類(lèi)級(jí)別的,它們對(duì)于每個(gè)對(duì)象實(shí)例都是相同的。序列化關(guān)注的是對(duì)象的實(shí)例狀態(tài),因?yàn)檫@是對(duì)象在不同環(huán)境中重建時(shí)所需的關(guān)鍵信息。
- 節(jié)省空間
靜態(tài)變量通常用于表示類(lèi)級(jí)別的常量或共享數(shù)據(jù),這些數(shù)據(jù)在所有對(duì)象實(shí)例之間是相同的。如果每個(gè)對(duì)象的靜態(tài)變量都被序列化并存儲(chǔ),將導(dǎo)致冗余,浪費(fèi)存儲(chǔ)空間。序列化的目標(biāo)之一是盡可能緊湊地保存對(duì)象的狀態(tài),因此不保存靜態(tài)變量是一種優(yōu)化。
- 不需要還原
靜態(tài)變量在類(lèi)加載時(shí)初始化,并在整個(gè)應(yīng)用程序的生命周期內(nèi)保持不變。因此,在反序列化時(shí)不需要重新初始化靜態(tài)變量。序列化和反序列化的目標(biāo)是保存和還原對(duì)象的動(dòng)態(tài)狀態(tài),而不是類(lèi)級(jí)別的靜態(tài)狀態(tài)。
序列化的安全性和性能考慮
在實(shí)際應(yīng)用中,需要注意序列化的安全性和性能。反序列化過(guò)程中可能存在安全風(fēng)險(xiǎn),因此要謹(jǐn)慎處理來(lái)自不受信任源的序列化數(shù)據(jù)。此外,對(duì)于大量數(shù)據(jù)的序列化,可能會(huì)影響系統(tǒng)性能,可以考慮使用更高效的序列化工具或壓縮算法。
總結(jié)
綜合來(lái)看,Java 序列化的核心思想是將對(duì)象的狀態(tài)轉(zhuǎn)換為字節(jié)流,并通過(guò) ObjectOutputStream 類(lèi)完成這一過(guò)程。該類(lèi)在內(nèi)部處理了對(duì)象引用的記錄、對(duì)象字段的寫(xiě)入、自定義寫(xiě)入方法的執(zhí)行等。在實(shí)際應(yīng)用中,我們需要注意序列化版本控制、對(duì)象字段的 transient 關(guān)鍵字的處理以及序列化性能等方面的問(wèn)題。
請(qǐng)注意,Java 序列化機(jī)制在現(xiàn)代應(yīng)用中可能會(huì)遇到一些挑戰(zhàn),包括性能問(wèn)題、安全性問(wèn)題以及與其他語(yǔ)言的兼容性等。因此,在一些場(chǎng)景下,開(kāi)發(fā)者可能會(huì)考慮使用其他序列化框架,如 JSON 或 Protocol Buffers,以滿(mǎn)足不同的需求。
以上就是Java序列化機(jī)制詳解的詳細(xì)內(nèi)容,更多關(guān)于Java序列化機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot利用自定義注解實(shí)現(xiàn)隱私數(shù)據(jù)脫敏(加密顯示)的解決方案
這兩天在整改等保測(cè)出的問(wèn)題,里面有一個(gè)“用戶(hù)信息泄露”的風(fēng)險(xiǎn)項(xiàng)(就是后臺(tái)系統(tǒng)里用戶(hù)的一些隱私數(shù)據(jù)直接明文顯示了),其實(shí)指的就是要做數(shù)據(jù)脫敏,本文給大家介紹了SpringBoot利用自定義注解實(shí)現(xiàn)隱私數(shù)據(jù)脫敏(加密顯示)的解決方案,需要的朋友可以參考下2023-11-11
java反射實(shí)現(xiàn)javabean轉(zhuǎn)json實(shí)例代碼
基于java反射機(jī)制實(shí)現(xiàn)javabean轉(zhuǎn)json字符串實(shí)例,大家參考使用吧2013-12-12
Mybatis-plus支持Gbase8s分頁(yè)的實(shí)現(xiàn)示例
本文主要介紹了Mybatis-plus支持Gbase8s分頁(yè)的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
Spring Security使用權(quán)限注解實(shí)現(xiàn)精確控制
在現(xiàn)代的應(yīng)用系統(tǒng)中,權(quán)限管理是確保系統(tǒng)安全性的重要環(huán)節(jié),Spring Security作為Java世界最為普及的安全框架,提供了強(qiáng)大而靈活的權(quán)限控制功能,這篇文章將深入探討Spring Security使用權(quán)限注解實(shí)現(xiàn)精確控制,需要的朋友可以參考下2024-12-12
Spring?Framework六種常見(jiàn)設(shè)計(jì)模式
設(shè)計(jì)模式是軟件開(kāi)發(fā)的重要組成部分,本文借助spring來(lái)講解這個(gè)框架的設(shè)計(jì)模式,通過(guò)本文我們探討了spring如何利用這些模式來(lái)提供這些豐富的功能,對(duì)本文感興趣的朋友跟隨小編一起看看吧2023-06-06
java中不同版本JSONObject區(qū)別小結(jié)
本文主要介紹了java中不同版本JSONObject區(qū)別小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02
java實(shí)現(xiàn)人工智能化屏幕監(jiān)控窗口
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)人工智能化屏幕監(jiān)控窗口,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09

