定義以及相關(guān)概念
由于在系統(tǒng)底層,數(shù)據(jù)的傳輸形式是簡單的字節(jié)序列形式傳遞,即在底層,系統(tǒng)不認(rèn)識(shí)對(duì)象,只認(rèn)識(shí)字節(jié)序列,而為了達(dá)到進(jìn)程通訊的目的,需要先將數(shù)據(jù)序列化,而序列化就是將對(duì)象轉(zhuǎn)化字節(jié)序列的過程。相反地,當(dāng)字節(jié)序列被運(yùn)到相應(yīng)的進(jìn)程的時(shí)候,進(jìn)程為了識(shí)別這些數(shù)據(jù),就要將其反序列化,即把字節(jié)序列轉(zhuǎn)化為對(duì)象
無論是在進(jìn)程間通信、本地?cái)?shù)據(jù)存儲(chǔ)又或者是網(wǎng)絡(luò)數(shù)據(jù)傳輸都離不開序列化的支持。而針對(duì)不同場景選擇合適的序列化方案對(duì)于應(yīng)用的性能有著極大的影響。
從廣義上講,數(shù)據(jù)序列化就是將數(shù)據(jù)結(jié)構(gòu)或者是對(duì)象轉(zhuǎn)換成我們可以存儲(chǔ)或者傳輸?shù)臄?shù)據(jù)格式的一個(gè)過程,在序列化的過程中,數(shù)據(jù)結(jié)構(gòu)或者對(duì)象將其狀態(tài)信息寫入到臨時(shí)或者持久性的存儲(chǔ)區(qū)中,而在對(duì)應(yīng)的反序列化過程中,則可以說是生成的數(shù)據(jù)被還原成數(shù)據(jù)結(jié)構(gòu)或?qū)ο蟮倪^程。
這樣來說,數(shù)據(jù)序列化相當(dāng)于是將我們?cè)鹊膶?duì)象序列化概念做出了擴(kuò)展,在對(duì)象序列化和反序列化中,我們熟知的有兩種方法,其一是Java語言中提供的Serializable接口,其二是Android提供的Parcelable接口。而在這里,因?yàn)槲覀儗?duì)這個(gè)概念做出了擴(kuò)展,因此也需要考慮幾種專門針對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行序列化的方法,如現(xiàn)在那些個(gè)開放API一般返回的數(shù)據(jù)都是JSON格式的,又或者是我們Android原生的SQLite數(shù)據(jù)庫來實(shí)現(xiàn)數(shù)據(jù)的本地存儲(chǔ),從廣義上來說,這些都可以算做是數(shù)據(jù)的序列化
序列化
將數(shù)據(jù)結(jié)構(gòu)或?qū)ο筠D(zhuǎn)換成二進(jìn)制串的過程。
反序列化
將在序列化過程中所生成的二進(jìn)制串轉(zhuǎn)換成數(shù)據(jù)結(jié)構(gòu)或者對(duì)象的過程
數(shù)據(jù)結(jié)構(gòu)、對(duì)象與二進(jìn)制串
不同的計(jì)算機(jī)語言中,數(shù)據(jù)結(jié)構(gòu),對(duì)象以及二進(jìn)制串的表示方式并不相同。
數(shù)據(jù)結(jié)構(gòu)和對(duì)象:對(duì)于類似 Java 這種完全面向?qū)ο蟮恼Z言,工程師所操作的一切都是對(duì)象(Object),來自于類的實(shí)例化。在 Java 語言中最接近數(shù)據(jù)結(jié)構(gòu)的概念,就是 POJO(Plain Old Java Object)或者 Javabean--那些只有 setter/getter 方法的類。而在 C 二進(jìn)制串:序列化所生成的二進(jìn)制串指的是存儲(chǔ)在內(nèi)存中的一塊數(shù)據(jù)。C 語言的字符串可以直接被傳輸層使用,因?yàn)槠浔举|(zhì)上就是以'0'結(jié)尾的存儲(chǔ)在內(nèi)存中的二進(jìn)制串。在 Java 語言里面,二進(jìn)制串的概念容易和 String 混淆。實(shí)際上String 是 Java 的一等公民,是一種特殊對(duì)象(Object)。對(duì)于跨語言間的通訊,序列化后的數(shù)據(jù)當(dāng)然不能是某種語言的特殊數(shù)據(jù)類型。二進(jìn)制串在 Java 里面所指的是 byte[],byte 是 Java 的 8 中原生數(shù)據(jù)類型之一(Primitive data types)。
序列化/反序列化的目的
簡單的概括
- 序列化: 主要用于網(wǎng)絡(luò)傳輸,數(shù)據(jù)持久化,一般序列化也稱為編碼(Encode)
- 反序列化: 主要用于從網(wǎng)絡(luò),磁盤上讀取字節(jié)數(shù)組還原成原始對(duì)象,一般反序列化也稱為解碼(Decode)
具體的講:
- 永久的保存對(duì)象數(shù)據(jù)(將對(duì)象數(shù)據(jù)保存在文件當(dāng)中,或者是磁盤中)
- 通過序列化操作將對(duì)象數(shù)據(jù)在網(wǎng)絡(luò)上進(jìn)行傳輸(由于網(wǎng)絡(luò)傳輸是以字節(jié)流的方式對(duì)數(shù)據(jù)進(jìn)行傳輸?shù)?因此序列化的目的是將對(duì)象數(shù)據(jù)轉(zhuǎn)換成字節(jié)流的形式)
- 將對(duì)象數(shù)據(jù)在進(jìn)程之間進(jìn)行傳遞(Activity之間傳遞對(duì)象數(shù)據(jù)時(shí),需要在當(dāng)前的Activity中對(duì)對(duì)象數(shù)據(jù)進(jìn)行序列化操作.在另一個(gè)Activity中需要進(jìn)行反序列化操作講數(shù)據(jù)取出)
- Java平臺(tái)允許我們?cè)趦?nèi)存中創(chuàng)建可復(fù)用的Java對(duì)象,但一般情況下,只有當(dāng)JVM處于運(yùn)行時(shí),這些對(duì)象才可能存在,即,這些對(duì)象的生命周期不會(huì)比JVM的生命周期更長(即每個(gè)對(duì)象都在JVM中)但在現(xiàn)實(shí)應(yīng)用中,就可能要停止JVM運(yùn)行,但有要保存某些指定的對(duì)象,并在將來重新讀取被保存的對(duì)象。這是Java對(duì)象序列化就能夠?qū)崿F(xiàn)該功能。(可選擇入數(shù)據(jù)庫、或文件的形式保存)
- 序列化對(duì)象的時(shí)候只是針對(duì)變量進(jìn)行序列化,不針對(duì)方法進(jìn)行序列化.
- 在Intent之間,基本的數(shù)據(jù)類型直接進(jìn)行相關(guān)傳遞即可,但是一旦數(shù)據(jù)類型比較復(fù)雜的時(shí)候,就需要進(jìn)行序列化操作了.
序列化協(xié)議特性
通用性
- 技術(shù)層面,序列化協(xié)議是否支持跨平臺(tái)、跨語言。如果不支持,在技術(shù)層面上的通用性就大大降低了。
- 流行程度,序列化和反序列化需要多方參與,很少人使用的協(xié)議往往意味著昂貴的學(xué)習(xí)成本;另一方面,流行度低的協(xié)議,往往缺乏穩(wěn)定而成熟的跨語言、跨平臺(tái)的公共包。
強(qiáng)健性 / 魯棒性
- 成熟度不夠
- 語言 / 平臺(tái)的不公平性
可調(diào)試性 / 可讀性
- 支持不到位
- 訪問限制
性能
性能包括兩個(gè)方面,時(shí)間復(fù)雜度和空間復(fù)雜度。
- 空間開銷(Verbosity), 序列化需要在原有的數(shù)據(jù)上加上描述字段,以為反序列化解析之用。如果序列化過程引入的額外開銷過高,可能會(huì)導(dǎo)致過大的網(wǎng)絡(luò),磁盤等各方面的壓力。對(duì)于海量分布式存儲(chǔ)系統(tǒng),數(shù)據(jù)量往往以 TB 為單位,巨大的的額外空間開銷意味著高昂的成本。
- 時(shí)間開銷(Complexity),復(fù)雜的序列化協(xié)議會(huì)導(dǎo)致較長的解析時(shí)間,這可能會(huì)使得序列化和反序列化階段成為整個(gè)系統(tǒng)的瓶頸。
可擴(kuò)展性 / 兼容性
移動(dòng)互聯(lián)時(shí)代,業(yè)務(wù)系統(tǒng)需求的更新周期變得更快,新的需求不斷涌現(xiàn),而老的系統(tǒng)還是需要繼續(xù)維護(hù)。如果序列化協(xié)議具有良好的可擴(kuò)展性,支持自動(dòng)增加新的業(yè)務(wù)字段,而不影響老的服務(wù),這將大大提供系統(tǒng)的靈活度。
安全性 / 訪問限制
在序列化選型的過程中,安全性的考慮往往發(fā)生在跨局域網(wǎng)訪問的場景。當(dāng)通訊發(fā)生在公司之間或者跨機(jī)房的時(shí)候,出于安全的考慮,對(duì)于跨局域網(wǎng)的訪問往往被限制為基于 HTTP/HTTPS 的 80 和 443 端口。如果使用的序列化協(xié)議沒有兼容而成熟的 HTTP 傳輸層框架支持,可能會(huì)導(dǎo)致以下三種結(jié)果之一:
- 因?yàn)樵L問限制而降低服務(wù)可用性;
- 被迫重新實(shí)現(xiàn)安全協(xié)議而導(dǎo)致實(shí)施成本大大提高;
- 開放更多的防火墻端口和協(xié)議訪問,而犧牲安全性
- 注意點(diǎn):Android的Parcelable也有安全漏洞(參考 https://www.anquanke.com/post/id/103570)
幾種常見的序列化和反序列化協(xié)議
XML&SOAP
XML 是一種常用的序列化和反序列化協(xié)議,具有跨機(jī)器,跨語言等優(yōu)點(diǎn),SOAP(Simple Object Access protocol) 是一種被廣泛應(yīng)用的,基于 XML 為序列化和反序列化協(xié)議的結(jié)構(gòu)化消息傳遞協(xié)議
JSON(Javascript Object Notation)
JSON 起源于弱類型語言 Javascript, 它的產(chǎn)生來自于一種稱之為"Associative array"的概念,其本質(zhì)是就是采用"Attribute-value"的方式來描述對(duì)象。實(shí)際上在 Javascript 和 PHP 等弱類型語言中,類的描述方式就是 Associative array。JSON 的如下優(yōu)點(diǎn),使得它快速成為最廣泛使用的序列化協(xié)議之一。
- 這種 Associative array 格式非常符合工程師對(duì)對(duì)象的理解。
- 它保持了 XML 的人眼可讀(Human-readable)的優(yōu)點(diǎn)。
- 相對(duì)于 XML 而言,序列化后的數(shù)據(jù)更加簡潔。 來自于的以下鏈接的研究表明:XML 所產(chǎn)生序列化之后文件的大小接近 JSON 的兩倍
- 它具備 Javascript 的先天性支持,所以被廣泛應(yīng)用于 Web browser 的應(yīng)用常景中,是 Ajax 的事實(shí)標(biāo)準(zhǔn)協(xié)議。
- 與 XML 相比,其協(xié)議比較簡單,解析速度比較快。
- 松散的 Associative array 使得其具有良好的可擴(kuò)展性和兼容性
Protobuf
Protobuf 具備了優(yōu)秀的序列化協(xié)議的所需的眾多典型特征。
- 標(biāo)準(zhǔn)的 IDL 和 IDL 編譯器,這使得其對(duì)工程師非常友好。
- 序列化數(shù)據(jù)非常簡潔,緊湊,與 XML 相比,其序列化之后的數(shù)據(jù)量約為 1/3 到 1/10。
- 解析速度非常快,比對(duì)應(yīng)的 XML 快約 20-100 倍。
- 提供了非常友好的動(dòng)態(tài)庫,使用非常簡介,反序列化只需要一行代碼。
Android程序員常用的序列化方案
Serializable接口
是 Java 提供的序列化接口,它是一個(gè)空接口:
public interface Serializable { }
Serializable 用來標(biāo)識(shí)當(dāng)前類可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列
化。
Serializable入門
public class Student implements Serializable {
//serialVersionUID唯一標(biāo)識(shí)了一個(gè)可序列化的類
private static final long serialVersionUID = -2100492893943893602L;
private String name;
private String sax;
private Integer age;
//Course也需要實(shí)現(xiàn)Serializable接口
private List<Course> courses;
//用transient關(guān)鍵字標(biāo)記的成員變量不參與序列化(在被反序列化后,transient 變量的值被
設(shè)為初始值,如 int 型的是 0,對(duì)象型的是 null)
private transient Date createTime;
//靜態(tài)成員變量屬于類不屬于對(duì)象,所以不會(huì)參與序列化(對(duì)象序列化保存的是對(duì)象的“狀態(tài)”,也
就是它的成員變量,因此序列化不會(huì)關(guān)注靜態(tài)變量)
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
public Student() {
System.out.println("Student: empty");
}
public Student(String name, String sax, Integer age) {
System.out.println("Student: " + name + " " + sax + " " + age);
this.name = name;
this.sax = sax;
this.age = age;
courses = new ArrayList<>();
createTime = new Date();
}
...
}
////Course也需要實(shí)現(xiàn)Serializable接口
public class Course implements Serializable {
private static final long serialVersionUID = 667279791530738499L;
private String name;
private float score;
...
}
Serializable 有以下幾個(gè)特點(diǎn):
- 可序列化類中,未實(shí)現(xiàn) Serializable 的屬性狀態(tài)無法被序列化/反序列化
- 也就是說,反序列化一個(gè)類的過程中,它的非可序列化的屬性將會(huì)調(diào)用無參構(gòu)造函數(shù)重新創(chuàng)建
- 因此這個(gè)屬性的無參構(gòu)造函數(shù)必須可以訪問,否者運(yùn)行時(shí)會(huì)報(bào)錯(cuò)
- 一個(gè)實(shí)現(xiàn)序列化的類,它的子類也是可序列化的
serialVersionUID與兼容性
- serialVersionUID的作用
serialVersionUID 用來表明類的不同版本間的兼容性。如果你修改了此類, 要修改此值。否則以前用老版本的類序列化的類恢復(fù)時(shí)會(huì)報(bào)錯(cuò): InvalidClassException
- 設(shè)置方式
在JDK中,可以利用JDK的bin目錄下的serialver.exe工具產(chǎn)生這個(gè)serialVersionUID,對(duì)于Test.class,執(zhí)行命令:serialver Test
- 兼容性問題
為了在反序列化時(shí),確保類版本的兼容性,最好在每個(gè)要序列化的類中加入 private static final long serialVersionUID這個(gè)屬性,具體數(shù)值自己定義。這樣,即使某個(gè)類在與之對(duì)應(yīng)的對(duì)象 已經(jīng)序列化出去后做了修改,該對(duì)象依然可以被正確反序列化。否則,如果不顯式定義該屬性,這個(gè)屬性值將由JVM根據(jù)類的相關(guān)信息計(jì)算,而修改后的類的計(jì)算 結(jié)果與修改前的類的計(jì)算結(jié)果往往不同,從而造成對(duì)象的反序列化因?yàn)轭惏姹静患嫒荻 ?/p>
不顯式定義這個(gè)屬性值的另一個(gè)壞處是,不利于程序在不同的JVM之間的移植。因?yàn)椴煌木幾g器實(shí)現(xiàn)該屬性值的計(jì)算策略可能不同,從而造成雖然類沒有改變,但是因?yàn)镴VM不同,出現(xiàn)因類版本不兼容而無法正確反序列化的現(xiàn)象出現(xiàn)
因此 JVM 規(guī)范強(qiáng)烈 建議我們手動(dòng)聲明一個(gè)版本號(hào),這個(gè)數(shù)字可以是隨機(jī)的,只要固定不變就可以。同時(shí)最好是 private 和 final 的,盡量保證不變。
Externalizable接口
public interface Externalizable extends Serializable {
void writeExternal(ObjectOutput var1) throws IOException;
void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException;
}
簡單使用
public class Course1 implements Externalizable {
private static final long serialVersionUID = 667279791530738499L;
private String name;
private float score;
...
@Override
public void writeExternal(ObjectOutput objectOutput) throws IOException
{
System.out.println("writeExternal");
objectOutput.writeObject(name);
objectOutput.writeFloat(score);
}
@Override
public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {
System.out.println("readExternal");
name = (String)objectInput.readObject();
score = objectInput.readFloat();
}
...
public static void main(String... args) throws Exception {
//TODO:
Course1 course = new Course1("英語", 12f);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(course);
course.setScore(78f);
);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bs));
Course1 course1 = (Course1) ois.readObject();
System.out.println("course1: " + course1);
}
序列化與反序列化 Serializable
Serializable 的序列化與反序列化分別通過 ObjectOutputStream 和 ObjectInputStream 進(jìn)行
/**
* 序列化對(duì)象
*
* @param obj
* @param path
* @return
*/
synchronized public static boolean saveObject(Object obj, String path) {
if (obj == null) {
return false;
}
ObjectOutputStream oos = null;
try {
// 創(chuàng)建序列化流對(duì)象
oos = new ObjectOutputStream(new FileOutputStream(path));
//序列化
oos.writeObject(obj);
oos.close();
return true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
// 釋放資源
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* 反序列化對(duì)象
*
* @param path
* @param <T>
* @return
*/
@SuppressWarnings("unchecked ")
synchronized public static <T> T readObject(String path) {
ObjectInputStream ojs = null;
try {
// 創(chuàng)建反序列化對(duì)象
ojs = new ObjectInputStream(new FileInputStream(path));
// 還原對(duì)象
return (T) ojs.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
if(ojs!=null){
try {
// 釋放資源
ojs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
Java的序列化步驟與數(shù)據(jù)結(jié)構(gòu)分析
序列化算法一般會(huì)按步驟做如下事情:
- 將對(duì)象實(shí)例相關(guān)的類元數(shù)據(jù)輸出。
- 遞歸地輸出類的超類描述直到不再有超類。
- 類元數(shù)據(jù)完了以后,開始從最頂層的超類開始輸出對(duì)象實(shí)例的實(shí)際數(shù)據(jù)值。
- 從上至下遞歸輸出實(shí)例的數(shù)據(jù)
格式化后以二進(jìn)制打開
aced 0005 7372 002e 636f 6d2e 7a65 726f
2e73 6572 6961 6c69 7a61 626c 6564 656d
6f2e 7365 7269 616c 697a 6162 6c65 2e53
7475 6465 6e74 e2d9 8cd7 833d f19e 0200
044c 0003 6167 6574 0013 4c6a 6176 612f
6c61 6e67 2f49 6e74 6567 6572 3b4c 0007
636f 7572 7365 7374 0010 4c6a 6176 612f
7574 696c 2f4c 6973 743b 4c00 046e 616d
6574 0012 4c6a 6176 612f 6c61 6e67 2f53
7472 696e 673b 4c00 0373 6178 7100 7e00
0378 7073 7200 116a 6176 612e 6c61 6e67
2e49 6e74 6567 6572 12e2 a0a4 f781 8738
0200 0149 0005 7661 6c75 6578 7200 106a
6176 612e 6c61 6e67 2e4e 756d 6265 7286
ac95 1d0b 94e0 8b02 0000 7870 0000 0012
7372 0013 6a61 7661 2e75 7469 6c2e 4172
7261 794c 6973 7478 81d2 1d99 c761 9d03
0001 4900 0473 697a 6578 7000 0000 0277
0400 0000 0273 7200 2d63 6f6d 2e7a 6572
6f2e 7365 7269 616c 697a 6162 6c65 6465
6d6f 2e73 6572 6961 6c69 7a61 626c 652e
436f 7572 7365 0942 a76f 5bfc 8343 0200
0246 0005 7363 6f72 654c 0004 6e61 6d65
7100 7e00 0378 7042 b466 6674 0006 e8af
ade6 9687 7371 007e 000a 42b2 999a 7400
06e6 95b0 e5ad a678 7400 045a 6572 6f74
0003 e794 b7
- AC ED: STREAM_MAGIC. 聲明使用了序列化協(xié)議.
- 00 05: STREAM_VERSION. 序列化協(xié)議版本.
- 0x73: TC_OBJECT. 聲明這是一個(gè)新的對(duì)象.
- 0x72: TC_CLASSDESC. 聲明這里開始一個(gè)新Class。
- 00 2e: Class名字的長度.
readObject/writeObject原理分析
以 oos.writeObject(obj) 為例分析
- ObjectOutputStream的構(gòu)造函數(shù)設(shè)置enableOverride = false
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;//enableOverride = false
...
}
- 所以writeObject方法執(zhí)行的是writeObject0(obj, false);
public final void writeObject(Object obj) throws IOException {
//enableOverride=false,不走這里
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {//一般情況都走這里
writeObject0(obj, false);
...
}
- 在writeObject0方法中,代碼非常多,看重點(diǎn)
/**
* Underlying writeObject/writeUnshared implementation.
*/
private void writeObject0(Object obj, boolean unshared) throws IOException
...
// 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 {
//如果沒有實(shí)現(xiàn)Serializable接口,會(huì)報(bào)NotSerializableException
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
...
}
- 在writeOrdinaryObject(obj, desc, unshared)方法中
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
...
if (desc.isExternalizable() && !desc.isProxy()) {
//如果對(duì)象實(shí)現(xiàn)了Externalizable接口,那么執(zhí)行
writeExternalData((Externalizable) obj)方法
writeExternalData((Externalizable) obj);
} else {
//如果對(duì)象實(shí)現(xiàn)的是Serializable接口,那么執(zhí)行的是
writeSerialData(obj, desc)
writeSerialData(obj, desc);
}
...
}
//這里我們看看writeExternalData
- writeSerialData方法,主要執(zhí)行方法:defaultWriteFields(obj, slotDesc)
/**
* Writes instance data for each serializable class of given object, from
* superclass to subclass.
* 最終寫序列化的方法
*/
private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException {
...
if (slotDesc.hasWriteObjectMethod()) {
//如果writeObjectMethod != null(目標(biāo)類中定義了私有的writeObject
方法),那么將調(diào)用目標(biāo)類中的writeObject方法
...
slotDesc.invokeWriteObject(obj, this);
...
} else {
//如果如果writeObjectMethod == null, 那么將調(diào)用默認(rèn)的
defaultWriteFields方法來讀取目標(biāo)類中的屬性
defaultWriteFields(obj, slotDesc);
}
}
}
- 在ObjectStreamClass中,ObjectOutputStream(ObjectInputStream)會(huì)尋找目標(biāo)類中的私有的 writeObject(readObject)方法,賦值給變量writeObjectMethod(readObjectMethod)
/**
* Creates local class descriptor representing given class.
*/
private ObjectStreamClass(final Class<?> cl) {
...
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
//,在序列化(反序列化)的時(shí)候,ObjectOutputStream(ObjectInputStream)
// 會(huì)尋找目標(biāo)類中的私有的writeObject(readObject)方法,
// 賦值給變量writeObjectMethod(readObjectMethod)
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject", new Class<?>[] { ObjectOutputStream.class }, Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject", new Class<?>[] { ObjectInputStream.class }, Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
domains = getProtectionDomains(cons, cl);
writeReplaceMethod = getInheritableMethod( cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod( cl, "readResolve", null, Object.class);
return null;
}
});
...
}
//ObjectStreamClass類中的一個(gè)判斷方法
boolean hasWriteObjectMethod() {
requireInitialized();
return (writeObjectMethod != null);
}
Serializable需要注意的坑
- 多引用寫入
public class Course implements Serializable {
private static final long serialVersionUID = 667279791530738499L;
private String name;
private float score;
...
public static void main(String... args) throws Exception {
//TODO:
Course course = new Course("英語", 12f);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(course);
course.setScore(78f);
// oos.reset();
oos.writeUnshared(course);
// oos.writeObject(course);
byte[] bs = out.toByteArray();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bs));
Course course1 = (Course) ois.readObject();
Course course2 = (Course) ois.readObject();
System.out.println("course1: " + course1);
System.out.println("course2: " + course2);
}
}
執(zhí)行結(jié)果:
course1: Course{name='英語', score=12.0}
course2: Course{name='英語', score=12.0}
在默認(rèn)情況下, 對(duì)于一個(gè)實(shí)例的多個(gè)引用,為了節(jié)省空間,只會(huì)寫入一次,后面會(huì)追加幾個(gè)字節(jié)代表某個(gè)實(shí)例的引用。
- 子類實(shí)現(xiàn)序列化,父類不實(shí)現(xiàn)序列化/ 對(duì)象引用
public class Person {
private String name;
private String sax;
// public Person() {
// }
public Person(String name, String sax) {
this.name = name;
this.sax = sax;
}
}
public class Student1 extends Person implements Serializable {
private static final long serialVersionUID = -2100492893943893602L;
private Integer age;
private List<Course> courses;
public Student1(String name, String sax, Integer age) {
super(name,sax);
...
}
...
public static void main(String ... args) throws Exception{
//TODO:
Student1 student = new Student1("Zero", "男", 18);
student.addCourse(new Course("語文", 90.2f));
//序列化
byte[] bytes = SerializeableUtils.serialize(student);
System.out.println(Arrays.toString(bytes));
//反序列化
//在readObject時(shí)拋出java.io.NotSerializableException異常。
//需要Person添加一個(gè)無參數(shù)構(gòu)造器
Student1 student1 = SerializeableUtils.deserialize(bytes);
System.out.println("Student: " + student1);
}
在readObject時(shí)拋出java.io.NotSerializableException異常。
- 類的演化
//反序列化目標(biāo)類多一個(gè)字段(height)
public class Student implements Serializable {
private static final long serialVersionUID = -2100492893943893602L;
private String name;
private String sax;
private Integer age;
private List<Course> courses;
...
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sax='" + sax + '\'' +
", age=" + age +
// ", height=" + height +
", courses=" + courses +
'}';
}
//private float height;
public static void main(String ... args)throws Exception{
//TODO:
String path = System.getProperty("user.dir") +"/a.out";
// Student student = new Student("Zero", "男", 18);
// student.addCourse(new Course("語文", 90.2f));
// //序列化
// SerializeableUtils.saveObject(student,path);
//反序列化
Student student1 = SerializeableUtils.readObject(path);
System.out.println("Student: " + student1);
}
}
執(zhí)行結(jié)果:
//序列化的時(shí)候
Student: Zero 男 18
Course: 語文 90.2
//反序列化的時(shí)候 添加一個(gè)float height
Student: Student{name='Zero', sax='男', age=18, height=0.0, courses=
[Course{name='語文', score=90.2}]}
可以看出反序列化之后,并沒有報(bào)錯(cuò),只是height實(shí)賦成了默認(rèn)值。類似的其它對(duì)象也會(huì)賦值為默認(rèn)值。
還有 相反,如果寫入的多一個(gè)字段,讀出的少一個(gè)字段,也是不會(huì)報(bào)錯(cuò)的
其它演化,比如更改類型等,這種演化本身就有問題,沒必再探討
- 枚舉類型
public enum Num {
TWO, ONE, THREE;
public void printValues() {
System.out.println(ONE + " ONE.ordinal " + ONE.ordinal());
System.out.println(TWO + " TWO.ordinal " + TWO.ordinal());
System.out.println(THREE + " THREE.ordinal " + THREE.ordinal());
}
public static void testSerializable() throws Exception {
File file = new File("p.dat");
// ObjectOutputStream oos = new ObjectOutputStream(new
FileOutputStream(file));
// oos.writeObject(Num.ONE);
// oos.close();
Num.ONE.printValues();
System.out.println("=========反序列化后=======");
ObjectInputStream ois = new ObjectInputStream(new
FileInputStream(file));
Num s1 = (Num) ois.readObject();
s1.printValues();
ois.close();
}
public static void main(String... args) throws Exception {
//TODO:
testSerializable();
}
}
執(zhí)行結(jié)果:
ONE ONE.ordinal 1
TWO TWO.ordinal 0
THREE THREE.ordinal 2
=========反序列化后=======
//調(diào)換(ONE,TWO)的位置: TWO, ONE, THREE; ->ONE, TWO, THREE;
ONE ONE.ordinal 0
TWO TWO.ordinal 1
THREE THREE.ordinal 2
可以看到ONE的值變成了0.
事實(shí)上序列化Enum對(duì)象時(shí),并不會(huì)保存元素的值,只會(huì)保存元素的name。這樣,在不依賴元素值的
前提下,ENUM對(duì)象如何更改都會(huì)保持兼容性。
重寫readObject,writeObject
“只有當(dāng)你自行設(shè)計(jì)的自定義序列化形式與默認(rèn)的序列化形式基本相同時(shí),才能接受默認(rèn)的序列化> 形式”.“當(dāng)一個(gè)對(duì)象的物理表示方法與它的邏輯數(shù)據(jù)內(nèi)容有實(shí)質(zhì)性差別時(shí),使用默認(rèn)序列化形式有> > N種缺陷”.其實(shí)從effective java的角度來講,是強(qiáng)烈建議我們重寫的,這樣有助于我們更好地把控> > 序列化過程,防范未知風(fēng)險(xiǎn)
public class Course3 implements Serializable {
private static final long serialVersionUID = 667279791530738499L;
private String name;
private float score;
...
private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException {
System.out.println("readObject");
inputStream.defaultReadObject();
name = (String)inputStream.readObject();
score = inputStream.readFloat();
}
private void writeObject(ObjectOutputStream outputStream) throws IOException {
System.out.println("writeObject");
outputStream.defaultWriteObject();
outputStream.writeObject(name);
outputStream.writeFloat(score);
}
private Object readResolve() {
System.out.println("readResolve");
return new Course3(name, 85f);
}
private Object writeReplace(){
System.out.println("writeReplace");
return new Course3(name +"replace",score);
}
...
public static void main(String... args) throws Exception {
//TODO:
Course3 course = new Course3("英語", 12f);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(course);
byte[] bs = out.toByteArray();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(bs));
Course3 course1 = (Course3) ois.readObject();
System.out.println("course1: " + course1);
}
}
執(zhí)行結(jié)果:
Course: 英語 12.0
writeReplace
Course: 英語replace 12.0
writeObject
readObject
readResolve
Course: 英語replace 85.0
course1: Course{name='英語replace', score=85.0}
- writeReplace 先于writeObject
- readResolve后于readObject
單例模式的序列化問題/反射問題
public class SingleTest {
static final String CurPath = System.getProperty("user.dir");
public static void main(String ... args) throws Exception {
//TODO:
Single instance = Single.getInstance();
System.out.println(instance.hashCode());
System.out.println(copyInstance(instance).hashCode());
System.out.println("=================反射======================");
//使用反射方式直接調(diào)用私有構(gòu)造器
Class<Single> clazz =
(Class<Single>)Class.forName("com.zero.serializabledemo.serializable.Single"
);
Constructor<Single> con = clazz.getDeclaredConstructor(null);
con.setAccessible(true);//繞過權(quán)限管理,即在true的情況下,可以通過構(gòu)造函數(shù)
新建對(duì)象
Single instance1 = con.newInstance();
Single instance2 = con.newInstance();
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
private static Single copyInstance(Single instance) throws Exception{
//序列化會(huì)導(dǎo)致單例失效
FileOutputStream fos = new FileOutputStream(CurPath+"/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new
FileInputStream(CurPath+"/a.txt"));
Single single2 = (Single)ois.readObject();
oos.close();
ois.close();
return single2;
}
}
class Single implements Serializable {
private static final long serialVersionUID = 1L;
private static boolean flag = false;
private Single(){
synchronized (Single.class) {
if (!flag) {
// flag = true;
} else {
throw new RuntimeException("單例模式被侵犯!");
}
}
}
private static Single single;
public static Single getInstance(){
if ( single == null ) {
synchronized (Single.class) {
if ( single == null ) {
single = new Single();
}
}
}
return single;
}
//如果不重寫readResolve,會(huì)導(dǎo)致單例模式在序列化->反序列化后失敗
// private Object readResolve() {
// return single;
// }
}
Parcelable接口
介紹Parcelable不得不先提一下Serializable接口,Serializable是Java為我們提供的一個(gè)標(biāo)準(zhǔn)化的序列化接口,那什么是序列化呢? ---- 簡單來說就是將對(duì)象轉(zhuǎn)換為可以傳輸?shù)亩M(jìn)制流(二進(jìn)制序列)的過程,這樣
我們就可以通過序列化,轉(zhuǎn)化為可以在網(wǎng)絡(luò)傳輸或者保存到本地的流(序列),從而進(jìn)行傳輸數(shù)據(jù) ,那反序列化就是從二進(jìn)制流(序列)轉(zhuǎn)化為對(duì)象的過程.
Parcelable是Android為我們提供的序列化的接口,Parcelable相對(duì)于Serializable的使用相對(duì)復(fù)雜一些,但Parcelable的效率相對(duì)Serializable也高很多,這一直是Google工程師引以為傲的,有時(shí)間的可以看一下
Parcelable和Serializable的效率對(duì)比 Parcelable vs Serializable 號(hào)稱快10倍的效率
Parcelable是Android SDK提供的,它是基于內(nèi)存的,由于內(nèi)存讀寫速度高于硬盤,因此Android中的跨進(jìn)程對(duì)象的傳遞一般使用Parcelable
Parcelable入門
public class Course implements Parcelable {
private String name;
private float score;
...
/**
* 描述當(dāng)前 Parcelable 實(shí)例的對(duì)象類型
* 比如說,如果對(duì)象中有文件描述符,這個(gè)方法就會(huì)返回上面的
CONTENTS_FILE_DESCRIPTOR
* 其他情況會(huì)返回一個(gè)位掩碼
* @return
*/
@Override
public int describeContents() {
return 0;
}
/**
* 將對(duì)象轉(zhuǎn)換成一個(gè) Parcel 對(duì)象
* @param dest 表示要寫入的 Parcel 對(duì)象
* @param flags 示這個(gè)對(duì)象將如何寫入
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeFloat(this.score);
}
protected Course(Parcel in) {
this.name = in.readString();
this.score = in.readFloat();
}
/**
* 實(shí)現(xiàn)類必須有一個(gè) Creator 屬性,用于反序列化,將 Parcel 對(duì)象轉(zhuǎn)換為 Parcelable
* @param <T>
*/
public static final Parcelable.Creator<Course> CREATOR = new Parcelable.Creator<Course>() {
//反序列化的方法,將Parcel還原成Java對(duì)象
@Override
public Course createFromParcel(Parcel source) {
return new Course(source);
}
//提供給外部類反序列化這個(gè)數(shù)組使用。
@Override
public Course[] newArray(int size) {
return new Course[size];
}
};
}
Parcel的簡介
在介紹之前我們需要先了解Parcel是什么?Parcel翻譯過來是打包的意思,其實(shí)就是包裝了我們需要傳輸?shù)臄?shù)據(jù),然后在Binder中傳輸,也就是用于跨進(jìn)程傳輸數(shù)據(jù)
簡單來說,Parcel提供了一套機(jī)制,可以將序列化之后的數(shù)據(jù)寫入到一個(gè)共享內(nèi)存中,其他進(jìn)程通過Parcel可以從這塊共享內(nèi)存中讀出字節(jié)流,并反序列化成對(duì)象,下圖是這個(gè)過程的模型。

Parcel可以包含原始數(shù)據(jù)類型(用各種對(duì)應(yīng)的方法寫入,比如writeInt(),writeFloat()等),可以包含Parcelable對(duì)象,它還包含了一個(gè)活動(dòng)的IBinder對(duì)象的引用,這個(gè)引用導(dǎo)致另一端接收到一個(gè)指向這個(gè)
IBinder的代理IBinder。
Parcelable通過Parcel實(shí)現(xiàn)了read和write的方法,從而實(shí)現(xiàn)序列化和反序列化
Parcelable與Serializable的性能比較
Serializable性能分析
Serializable是Java中的序列化接口,其使用起來簡單但開銷較大(因?yàn)镾erializable在序列化過程中使用了反射機(jī)制,故而會(huì)產(chǎn)生大量的臨時(shí)變量,從而導(dǎo)致頻繁的GC),并且在讀寫數(shù)據(jù)過程中,它是通
過IO流的形式將數(shù)據(jù)寫入到硬盤或者傳輸?shù)骄W(wǎng)絡(luò)上。
Parcelable性能分析
Parcelable則是以IBinder作為信息載體,在內(nèi)存上開銷比較小,因此在內(nèi)存之間進(jìn)行數(shù)據(jù)傳遞時(shí),推薦使用Parcelable,而Parcelable對(duì)數(shù)據(jù)進(jìn)行持久化或者網(wǎng)絡(luò)傳輸時(shí)操作復(fù)雜,一般這個(gè)時(shí)候推薦使用
Serializable。
性能比較總結(jié)描述
首先Parcelable的性能要強(qiáng)于Serializable的原因我需要簡單的闡述一下
- 在內(nèi)存的使用中,前者在性能方面要強(qiáng)于后者
- 后者在序列化操作的時(shí)候會(huì)產(chǎn)生大量的臨時(shí)變量,(原因是使用了反射機(jī)制)從而導(dǎo)致GC的頻繁調(diào)用,因此在性能上會(huì)稍微遜色
- Parcelable是以Ibinder作為信息載體的.在內(nèi)存上的開銷比較小,因此在內(nèi)存之間進(jìn)行數(shù)據(jù)傳遞的時(shí)候,Android推薦使用Parcelable,既然是內(nèi)存方面比價(jià)有優(yōu)勢,那么自然就要優(yōu)先選擇.
- 在讀寫數(shù)據(jù)的時(shí)候,Parcelable是在內(nèi)存中直接進(jìn)行讀寫,而Serializable是通過使用IO流的形式將數(shù)據(jù)讀寫入在硬盤上.但是:雖然Parcelable的性能要強(qiáng)于Serializable,但是仍然有特殊的情況需要使用Serializable,而不去使用Parcelable,因?yàn)镻arcelable無法將數(shù)據(jù)進(jìn)行持久化,因此在將數(shù)據(jù)保存在磁盤的時(shí)候,仍然需要使用后者,因?yàn)榍罢邿o法很好的將數(shù)據(jù)進(jìn)行持久化.(原因是在不同的Android版本當(dāng)中,Parcelable可能會(huì)不同,因此數(shù)據(jù)的持久化方面仍然是使用Serializable)
性能測試方法分析
- 通過將一個(gè)對(duì)象放到一個(gè)bundle里面然后調(diào)用Bundle#writeToParcel(Parcel, int)方法來模擬傳遞對(duì)象給一個(gè)activity的過程,然后再把這個(gè)對(duì)象取出來。
- 在一個(gè)循環(huán)里面運(yùn)行1000 次。
- 兩種方法分別運(yùn)行10次來減少內(nèi)存整理,cpu被其他應(yīng)用占用等情況的干擾。
- 參與測試的對(duì)象就是上面的相關(guān)代碼
- 在多種Android軟硬件環(huán)境上進(jìn)行測試
兩種如何選擇
- 在使用內(nèi)存方面,Parcelable比Serializable性能高,所以推薦使用Parcelable。
- Serializable在序列化的時(shí)候會(huì)產(chǎn)生大量的臨時(shí)變量,從而引起頻繁的GC。
- Parcelable不能使用在要將數(shù)據(jù)存儲(chǔ)在磁盤上的情況,因?yàn)镻arcelable不能很好的保證數(shù)據(jù)的持續(xù)性,在外界有變化的情況下,建議使用Serializable
SQLite 與 SharedPreferences
- SQLite主要用于存儲(chǔ)復(fù)雜的關(guān)系型數(shù)據(jù),Android支持原生支持SQLite數(shù)據(jù)庫相關(guān)操作(SQLiteOpenHelper),不過由于原生API接口并不友好,所以產(chǎn)生了不少封裝了SQLite的ORM框架。
- SharedPreferences是Android平臺(tái)上提供的一個(gè)輕量級(jí)存儲(chǔ)API,一般用于存儲(chǔ)常用的配置信息,其本質(zhì)是一個(gè)鍵值對(duì)存儲(chǔ),支持常用的數(shù)據(jù)類型如boolean、float、int、long以及String的存儲(chǔ)和讀取。
Android里面為什么要設(shè)計(jì)出Bundle而不是直接用Map結(jié)構(gòu)
Bundle內(nèi)部是由ArrayMap實(shí)現(xiàn)的,ArrayMap的內(nèi)部實(shí)現(xiàn)是兩個(gè)數(shù)組,一個(gè)int數(shù)組是存儲(chǔ)對(duì)象數(shù)據(jù)對(duì)應(yīng)下標(biāo),一個(gè)對(duì)象數(shù)組保存key和value,內(nèi)部使用二分法對(duì)key進(jìn)行排序,所以在添加、刪除、查找數(shù)據(jù)的時(shí)候,都會(huì)使用二分法查找,只適合于小數(shù)據(jù)量操作,如果在數(shù)據(jù)量比較大的情況下,那么它的性能將退化。而HashMap內(nèi)部則是數(shù)組+鏈表結(jié)構(gòu),所以在數(shù)據(jù)量較少的時(shí)候,HashMap的Entry Array比ArrayMap占用更多的內(nèi)存。因?yàn)槭褂肂undle的場景大多數(shù)為小數(shù)據(jù)量,我沒見過在兩個(gè)Activity之間傳遞10個(gè)以上數(shù)據(jù)的場景,所以相比之下,在這種情況下使用ArrayMap保存數(shù)據(jù),在操作速度和內(nèi)存占用上都具有優(yōu)勢,因此使用Bundle來傳遞數(shù)據(jù),可以保證更快的速度和更少的內(nèi)存占用。
另外一個(gè)原因,則是在Android中如果使用Intent來攜帶數(shù)據(jù)的話,需要數(shù)據(jù)是基本類型或者是可序列化類型,HashMap使用Serializable進(jìn)行序列化,而Bundle則是使用Parcelable進(jìn)行序列化。而在Android平臺(tái)中,更推薦使用Parcelable實(shí)現(xiàn)序列化,雖然寫法復(fù)雜,但是開銷更小,所以為了更加快速的進(jìn)行數(shù)據(jù)的序列化和反序列化,系統(tǒng)封裝了Bundle類,方便我們進(jìn)行數(shù)據(jù)的傳輸。
Android中Intent/Bundle的通信原理及大小限制
Intent 中的 Bundle 是使用 Binder 機(jī)制進(jìn)行數(shù)據(jù)傳送的。能使用的 Binder 的緩沖區(qū)是有大小限制的(有些手機(jī)是 2 M),而一個(gè)進(jìn)程默認(rèn)有 16 個(gè) Binder 線程,所以一個(gè)線程能占用的緩沖區(qū)就更小了( 有人以前做過測試,大約一個(gè)線程可以占用 128 KB)。所以當(dāng)你看到 The Binder transaction failed because it was too large 這類 TransactionTooLargeException 異常時(shí),你應(yīng)該知道怎么解決了
為何Intent不能直接在組件間傳遞對(duì)象而要通過序列化機(jī)制?
Intent在啟動(dòng)其他組件時(shí),會(huì)離開當(dāng)前應(yīng)用程序進(jìn)程,進(jìn)入ActivityManagerService進(jìn)程(intent.prepareToLeaveProcess()),這也就意味著,Intent所攜帶的數(shù)據(jù)要能夠在不同進(jìn)程間傳輸。首先我們知道,Android是基于Linux系統(tǒng),不同進(jìn)程之間的java對(duì)象是無法傳輸,所以我們此處要對(duì)對(duì)象進(jìn)行序列化,從而實(shí)現(xiàn)對(duì)象在 應(yīng)用程序進(jìn)程 和 ActivityManagerService進(jìn)程 之間傳輸。
而Parcel或者Serializable都可以將對(duì)象序列化,其中,Serializable使用方便,但性能不如Parcel容器,后者也是Android系統(tǒng)專門推出的用于進(jìn)程間通信等的接口
