宗旨
本系列意在用最淺顯的文字和代碼示例柑爸,讓大家實實在在的掌握J(rèn)ava最基礎(chǔ)的知識让腹。
一肾砂、前言
我們在學(xué)習(xí)JDK源碼時汛兜,如果大家有留心,應(yīng)該會經(jīng)惩ń瘢看到關(guān)鍵字:transient !但不知道有多少人去真正了解過該關(guān)鍵字的作用肛根。
二辫塌、初識transient
transient,中文意思:短暫的派哲!那么臼氨,它有什么用呢?這就涉及到 Java 持久化機(jī)制芭届。
2.1储矩、Java 持久化
Java為我們提供了一種非常方便的持久化實例機(jī)制:Serializable !
可能有人要說:Serializable 不是用于序列化與反序列化么褂乍?
嗯持隧,對!沒錯逃片,那我想問:一個實例序列化后干啥用屡拨?
一個實例的序列化,可用于數(shù)據(jù)傳輸,然而呀狼,其最大的作用還是用于寫入磁盤裂允,從而防止數(shù)據(jù)丟失。
2.2毙替、Java 序列化
我們在編寫可用于序列化/反序列化的類時抱既,都會去實現(xiàn) Serializable框弛,但是有時候,我們又不想序列化某些字段十饥,那么,我們就可以用關(guān)鍵字 transient 來修飾該字段哩俭。
// BankCard.java
import java.io.Serializable;
public class BankCard implements Serializable {
private String cardNo;
private transient String password;
public String getCardNo() {
return cardNo;
}
public void setCardNo(String cardNo) {
this.cardNo = cardNo;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "BankCard{cardNo='" + cardNo + '\'' + ", password='" + password + '\'' + '}';
}
}
編寫測試類
public class Main {
public static void main(String[] args) {
try {
serializeBankCard();
Thread.sleep(2000);
deSerializeBankCard();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void serializeBankCard() throws Exception {
BankCard bankCard = new BankCard();
bankCard.setCardNo("CardNo: 1234567890");
bankCard.setPassword("********");
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("./bank.txt"));
os.writeObject(bankCard);
os.close();
System.out.println("序列化 " + bankCard.toString());
}
private static void deSerializeBankCard() throws Exception {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("./bank.txt"));
BankCard bankCard = (BankCard) is.readObject();
is.close();
System.out.println("反序列化 " + bankCard.toString());
}
}
// 打印輸出
// 序列化 BankCard{cardNo='CardNo: 1234567890', password='********'}
// 反序列化 BankCard{cardNo='CardNo: 1234567890', password='null'}
我們可以看到绷跑,我們先創(chuàng)建『銀行卡』類,然后將數(shù)據(jù)序列化寫到磁盤上凡资,過了2秒后砸捏,我們再從磁盤上讀取數(shù)據(jù),打印發(fā)現(xiàn)隙赁,『password』字段是『null』垦藏。
我們打開磁盤上的文件,用十六進(jìn)制查看數(shù)據(jù)伞访,也可以看到掂骏,文件中,只有『CardNo』厚掷,沒有『password』:
三弟灼、深入分析 transient
在深入分析之前,我先拋出幾個問題冒黑,然后田绑,再帶大家一一去解惑:
- transient 實現(xiàn)原理;
- transient 修飾的字段真的無法被序列化抡爹?
- 靜態(tài)變量能被序列化么掩驱?
- transient + static 修飾的字段能被序列化么?
OK冬竟,帶著以上問題欧穴,我們?nèi)タ丛创a!
3.1泵殴、Serializable 類
/**
* ......
* The writeObject method is responsible for writing the state of the
* object for its particular class so that the corresponding
* readObject method can restore it. The default mechanism for saving
* the Object's fields can be invoked by calling
* out.defaultWriteObject. The method does not need to concern
* itself with the state belonging to its superclasses or subclasses.
* State is saved by writing the individual fields to the
* ObjectOutputStream using the writeObject method or by using the
* methods for primitive data types supported by DataOutput.
*
* The readObject method is responsible for reading from the stream and
* restoring the classes fields. It may call in.defaultReadObject to invoke
* the default mechanism for restoring the object's non-static and
* non-transient fields.
*
* .........
*
* @see java.io.ObjectOutputStream
* @see java.io.ObjectInputStream
* @see java.io.ObjectOutput
* @see java.io.ObjectInput
* @see java.io.Externalizable
* @since JDK1.1
*/
public interface Serializable {
}
Serializable 的注釋就說到了:
- writeObject 負(fù)責(zé)寫數(shù)據(jù)涮帘,readObject 負(fù)責(zé)讀數(shù)據(jù);
- 數(shù)據(jù)(狀態(tài))的讀寫笑诅,并不關(guān)心它的父類或子類焚辅;
- ObjectOutputSteam 是基于 DataOutput 實現(xiàn)的寫映屋;
- readObject 負(fù)責(zé)讀流(stream),并存到類成員變量中同蜻,它的默認(rèn)存到非static成員和非transient成員中棚点;
3.2、ObjectOutputStream 類
開頭的注釋也說了:默認(rèn)的 Serializable 機(jī)制湾蔓,只寫類的對象瘫析、簽名以及非 transient 和非 static 字段
/*
* The default serialization mechanism for an object writes the class of the
* object, the class signature, and the values of all non-transient and
* non-static fields.
*/
public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants {
private final BlockDataOutputStream bout;
private static class BlockDataOutputStream extends OutputStream implements DataOutput {
......
}
public final void writeObject(Object obj) throws IOException {
......
try {
writeObject0(obj, false);
}
......
}
private void writeObject0(Object obj, boolean unshared) throws IOException {
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
......
ObjectStreamClass desc;
for (;;) {
......
// 重點(diǎn):獲取 Serializable對象的 desc
// desc 中的 fields 會過濾 static 和 transient
// 具體代碼流程繼續(xù)看 3.3
desc = ObjectStreamClass.lookup(cl, true);
......
}
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared); // obj 根據(jù) desc.fields 序列化數(shù)據(jù)
}
......
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
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 {
desc.checkSerialize();
// 最終調(diào)用 bout 來寫數(shù)據(jù)
bout.writeByte(TC_OBJECT);
// 所有的 writeXXX 都將調(diào)用 bout 來寫數(shù)據(jù)
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();
}
}
}
}
3.3、ObjectStreamClass類
3.3.1默责、ObjectStreamClass.lookup
public class ObjectStreamClass implements Serializable {
static ObjectStreamClass lookup(Class<?> cl, boolean all) {
......
if (entry == null) {
try {
entry = new ObjectStreamClass(cl);
} catch (Throwable th) {
entry = th;
}
......
}
......
}
}
3.3.2贬循、ObjectStreamClass構(gòu)造函數(shù)
public class ObjectStreamClass implements Serializable {
private ObjectStreamClass(final Class<?> cl) {
......
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
......
try {
fields = getSerialFields(cl); // 獲取可序列化的字段
computeFieldOffsets();
} catch (InvalidClassException e) {
......
fields = NO_FIELDS;
}
......
}
}
}
}
}
3.3.3、ObjectStreamClass.getSerialFields
public class ObjectStreamClass implements Serializable {
private static ObjectStreamField[] getSerialFields(Class<?> cl) throws InvalidClassException {
ObjectStreamField[] fields;
if (Serializable.class.isAssignableFrom(cl) &&
!Externalizable.class.isAssignableFrom(cl) &&
!Proxy.isProxyClass(cl) &&
!cl.isInterface())
{
if ((fields = getDeclaredSerialFields(cl)) == null) {
fields = getDefaultSerialFields(cl);
}
Arrays.sort(fields);
} else {
fields = NO_FIELDS;
}
return fields;
}
}
3.3.4桃序、ObjectStreamClass.getDefaultSerialFields
public class ObjectStreamClass implements Serializable {
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
Field[] clFields = cl.getDeclaredFields();
ArrayList<ObjectStreamField> list = new ArrayList<>();
int mask = Modifier.STATIC | Modifier.TRANSIENT; // 重點(diǎn)1:掩碼 mask
for (int i = 0; i < clFields.length; i++) {
// 重點(diǎn)2:過濾 static 或者 transient
if ((clFields[i].getModifiers() & mask) == 0) {
list.add(new ObjectStreamField(clFields[i], false, true));
}
}
int size = list.size();
return (size == 0) ? NO_FIELDS : list.toArray(new ObjectStreamField[size]);
}
}
3.4杖虾、斷點(diǎn)查看運(yùn)行時JDK源碼
簡單的看完以上源碼,至少回答了我們這么幾個問題:
- static 變量是無法序列化的媒熊,無論是否加了 transient 修飾符奇适;
- transient 修飾的變量也是無法序列化的;
那么芦鳍,還有一個問題嚷往,transient 修飾的變量真的無法序列化么?
再回答該問題前柠衅,首先我想問下大家皮仁,大家知道 Java 除了提供 Serializable 序列化外,還有提供其它方式么菲宴?
答案是:Java 提供了兩種序列化方式
- Serializable贷祈,序列化與反序列化由 Java 實現(xiàn)(ObjectOutputStream / ObjectInputStream 的默認(rèn)機(jī)制);
- Externalizable喝峦,序列化與反序列化由開發(fā)者自主來實現(xiàn)势誊;
先來看看示例
// BankCardExt.java
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class BankCardExt implements Externalizable {
private String cardNo;
private transient String password;
public String getCardNo() {
return cardNo;
}
public void setCardNo(String cardNo) {
this.cardNo = cardNo;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(cardNo);
out.writeObject(password);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
cardNo = (String) in.readObject();
password = (String) in.readObject();
}
@Override
public String toString() {
return "BankCardExt{cardNo='" + cardNo + "\', password='" + password + "\'}";
}
}
這個類的讀和寫,由我們來實現(xiàn)愈犹。
再來個測試類
public class Main {
public static void main(String[] args) {
try {
serializeBankCard();
Thread.sleep(2000);
deSerializeBankCard();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void serializeBankCard() throws Exception {
BankCardExt bankCard = new BankCardExt();
bankCard.setCardNo("CardNo: 1234567890");
bankCard.setPassword("********");
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("./bankExt.txt"));
os.writeObject(bankCard);
os.close();
System.out.println("序列化 " + bankCard.toString());
}
private static void deSerializeBankCard() throws Exception {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("./bankExt.txt"));
BankCardExt bankCard = (BankCardExt) is.readObject();
is.close();
System.out.println("反序列化 " + bankCard.toString());
}
}
// 打印輸出
// 序列化 BankCardExt{cardNo='CardNo: 1234567890', password='********'}
// 反序列化 BankCardExt{cardNo='CardNo: 1234567890', password='********'}
我們看到,讀的時候闻丑,數(shù)據(jù)也回來了漩怎。
查看文件,我們也看到了『CardNo』和『password』嗦嗡。
這里還有一點(diǎn)需要注意:寫的順序與讀的順序要保持一致勋锤!如果我們順充不一致,結(jié)果將如下:
{
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(cardNo);
out.writeObject(password);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
password = (String) in.readObject();
cardNo = (String) in.readObject();
}
}
// 打印輸出
// 序列化 BankCardExt{cardNo='CardNo: 1234567890', password='********'}
// 反序列化 BankCardExt{cardNo='********', password='CardNo: 1234567890'}
OK侥祭,最后一個問題我們也解答了叁执,當(dāng)我們實現(xiàn) Externalizable 時茄厘,需要我們自主選擇哪些字段需要序列化與反序列化,因此谈宛,此時的 transient 修飾符將沒有作用次哈。
四、總結(jié)
本文分析了 transient 的序列化底層機(jī)制:
- 當(dāng)我們的類實現(xiàn)了 Serializable 時吆录,transient 修飾的變量只能存在于內(nèi)存中窑滞,ObjectOutputStream 將忽略 transient 和 static 修飾的變量;
- 當(dāng)我們的類實現(xiàn)了 Externalizable 時恢筝,此時 transient 關(guān)鍵字將失效哀卫,類的成員變量的序列化與反序列化將由我們自己控制;