簡(jiǎn)述
需要將對(duì)象持久化到我們的存儲(chǔ)設(shè)備上或者通過(guò)網(wǎng)絡(luò)傳輸?shù)狡渌蛻?hù)端,那么我們就需要序列化。
Serializable為Java中帶的一種序列化方式,使用起來(lái)非常簡(jiǎn)單。只需要讓類(lèi)實(shí)現(xiàn)一個(gè)Serializable接口就可以進(jìn)行序列化了。
使用
先講初步使用,后面會(huì)講到serialVersionUID。
- 定義一個(gè)User類(lèi),實(shí)現(xiàn)Serializable接口
public class User implements Serializable {
private String name;
private int age;
public User(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;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 操作User類(lèi),持久化到cache.txt文件中,并從文件中讀出來(lái)
//創(chuàng)建到User類(lèi)
User user = new User("大灰狼", 18);
try {
File file = new File("cache.txt");
if (!file.exists()) {
file.createNewFile();
}
//將User類(lèi)序列化到文件中
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
objectOutputStream.writeObject(user);
System.out.println("序列化成功");
//從文件中讀出來(lái)
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
User user1 = (User) objectInputStream.readObject();
System.out.println("" + user1);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
就這樣,最簡(jiǎn)單到使用就完成了。
serialVersionUID
如果,咱們某一天改了User類(lèi)的內(nèi)部結(jié)構(gòu),如增加了一個(gè)字段:boolean isMan,但是文件類(lèi)已經(jīng)持久化的數(shù)據(jù)并沒(méi)有改動(dòng),我們需要反序列化讀出來(lái)
- 讀取代碼
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
User user1 = (User) objectInputStream.readObject();
System.out.println("" + user1);
我們會(huì)很明顯的看到報(bào)出一個(gè)錯(cuò)誤:
java.io.InvalidClassException: serial.User; local class incompatible: stream classdesc serialVersionUID = -3951066851478225822, local class serialVersionUID = -8381808064053356693
- 原因:因?yàn)樵蹅兇鎯?chǔ)的時(shí)候,有幫我們自動(dòng)生成一個(gè)serialVersionUID就是-3951066851478225822,可以當(dāng)作存儲(chǔ)在文件中的類(lèi)的版本標(biāo)示。而我們本地類(lèi)版本升級(jí)了,自動(dòng)幫我們修改了版本標(biāo)示。在反序列化的時(shí)候會(huì)檢測(cè)這個(gè)版本標(biāo)示是否相同,所以在反序列化的時(shí)候就通不過(guò)了,這時(shí)候就不能反序列化了。
我們自然不想看到這種情況出現(xiàn),所以通常情況下,我們應(yīng)該手動(dòng)指定serialVersionUID的值,如123L
只需要在User中添加:
private static final long serialVersionUID = 123L;
那我們重新試試(前面的文件不能用了,刪除后再來(lái)).
先寫(xiě)文件,只包含name和age。
然后我們?cè)偕?jí)User類(lèi),在其中添了兩個(gè)字段如下:
private boolean isMan;
private String address;
- 再讀一次文件,打印如下
User{name='大灰狼', age=18, isMan=false, address='null'}
可以看到能成功讀取了,只是缺失字段使用了默認(rèn)值代替,做到了最大限度的恢復(fù)數(shù)據(jù)。
注意:類(lèi)升級(jí)指的是內(nèi)部結(jié)構(gòu),如果改了類(lèi)名,那是怎么都反序列化不回來(lái)的,因?yàn)轭?lèi)結(jié)構(gòu)有了毀滅性的改變,那錯(cuò)誤將是這樣的:
java.lang.ClassNotFoundException: serial.User
重寫(xiě)序列化和反序列化
就如同我們調(diào)用一樣,序列化過(guò)程其實(shí)就是通過(guò)ObjectOutputStream的writeObject、readObject來(lái)體現(xiàn)的。我們跟蹤下流程,可以看到調(diào)用到了要序列化對(duì)象的私有方法writeObject,部分代碼如下:
- 從ObjectOutputStream進(jìn)行寫(xiě)方法
public final void writeObject(Object obj) throws IOException {
try {
//調(diào)用該方法執(zhí)行寫(xiě)
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
- writeObject0:
private void writeObject0(Object obj, boolean unshared){
for (;;) {
// REMIND: skip this check for strings/arrays?
Class<?> repCl;
//查找類(lèi)信息,這里明顯使用到了反射來(lái)查找
desc = ObjectStreamClass.lookup(cl, true);
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl)
{
break;
}
cl = repCl;
}
}
- ObjectStreamClass.lookup
static ObjectStreamClass lookup(Class<?> cl, boolean all) {
try {
//創(chuàng)建流對(duì)象
entry = new ObjectStreamClass(cl);
} catch (Throwable th) {
entry = th;
}
}
- 創(chuàng)建流對(duì)象,尋找到要序列化對(duì)象中的私有方法,得到writeObjectMethod和readObjectMethod
private ObjectStreamClass(final Class<?> cl) {
//獲取對(duì)象中的私有方法,writeObject和readObject
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
}
- 在執(zhí)行寫(xiě)序列化數(shù)據(jù)時(shí)調(diào)用到方法:writeSerialData
private void writeSerialData(Object obj, ObjectStreamClass desc)
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
//執(zhí)行到了創(chuàng)建好流對(duì)象的方法
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
- invokeWriteObject調(diào)用到了writeObjectMethod.invoke
void invokeWriteObject(Object obj, ObjectOutputStream out)
throws IOException, UnsupportedOperationException
{
if (writeObjectMethod != null) {
try {
//通過(guò)反射執(zhí)行了要序列化對(duì)象的writeObject方法
writeObjectMethod.invoke(obj, new Object[]{ out });
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof IOException) {
throw (IOException) th;
} else {
throwMiscException(th);
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
到這里我們就能看到我們要序列化對(duì)象的writeObject也被調(diào)用了,readObject過(guò)程分析也是一樣的。通過(guò)這樣我們可以想象,是不是我們就可以自己實(shí)現(xiàn)這個(gè)方法來(lái)重寫(xiě)寫(xiě)數(shù)據(jù)呢?答案肯定是的,哈哈哈.
重寫(xiě)writeObject和readObject:
- 改isMan默認(rèn)不序列化
- 重寫(xiě)寫(xiě)和讀方法
public class User implements Serializable {
private static final long serialVersionUID = 123L;
private String name;
private int age;
private transient boolean isMan;
private String address;
public User(String name, int age, boolean isMan, String address) {
this.name = name;
this.age = age;
this.isMan = isMan;
this.address = address;
}
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;
}
public boolean isMan() {
return isMan;
}
public void setMan(boolean man) {
isMan = man;
}
public static long getSerialVersionUID() {
return serialVersionUID;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", isMan=" + isMan +
", address='" + address + '\'' +
'}';
}
/**
* 重寫(xiě)寫(xiě)方法
*
* @param oos
* @throws IOException
*/
private void writeObject(ObjectOutputStream oos) throws IOException {
//調(diào)用默認(rèn)的序列化方法,如里面注釋一樣,可以把非靜態(tài)和非transient字段給序列化了
oos.defaultWriteObject();
//把isMan也序列化一下
oos.writeBoolean(isMan);
System.out.println("序列化成功");
}
/**
* 重寫(xiě)讀方法
*
* @param ois
* @throws IOException
* @throws ClassNotFoundException
*/
private void readObject(ObjectInputStream ois) throws IOException,
ClassNotFoundException {
//調(diào)用默認(rèn)的反序列化方法,如里面注釋一樣,可以把非靜態(tài)和非transient字段給反序列化了
ois.defaultReadObject();
//讀取序列化的字段
isMan = ois.readBoolean();
System.out.println("反序列化成功");
}
}
再進(jìn)行序列化和反序列化,就能看到isMan咱們也能夠正常寫(xiě)入和讀了。
序列化是持久化的一個(gè)必備品,記錄到這里大概對(duì)Serializable就更熟悉了。