序列化定義以及相關(guān)概念
定義:將數(shù)據(jù)結(jié)構(gòu)或?qū)ο筠D(zhuǎn)換成二進(jìn)制串的過(guò)程。
由于在系統(tǒng)底層,數(shù)據(jù)的傳輸形式是簡(jiǎn)單的字節(jié)序列形式傳遞,即在底層,系統(tǒng)不認(rèn)識(shí)對(duì)象,只認(rèn)識(shí)字節(jié)序列,而為了達(dá)到進(jìn)程通訊的目的,需要先將數(shù)據(jù)序列化,而序列化就是將對(duì)象轉(zhuǎn)化字節(jié)序列的過(guò)程。相反地,當(dāng)字節(jié)序列被運(yùn)到相應(yīng)的進(jìn)程的時(shí)候,進(jìn)程為了識(shí)別這些數(shù)據(jù),就要將其反序列化,即把字節(jié)序列轉(zhuǎn)化為對(duì)象
無(wú)論是在進(jìn)程間通信、本地?cái)?shù)據(jù)存儲(chǔ)又或者是網(wǎng)絡(luò)數(shù)據(jù)傳輸都離不開序列化的支持。而針對(duì)不同場(chǎng)景選擇合適的序列化方案對(duì)于應(yīng)用的性能有著極大的影響。
從廣義上講,數(shù)據(jù)序列化就是將數(shù)據(jù)結(jié)構(gòu)或者是對(duì)象轉(zhuǎn)換成我們可以存儲(chǔ)或者傳輸?shù)臄?shù)據(jù)格式的一個(gè)過(guò)程,在序列化的過(guò)程中,數(shù)據(jù)結(jié)構(gòu)或者對(duì)象將其狀態(tài)信息寫入到臨時(shí)或者持久性的存儲(chǔ)區(qū)中,而在對(duì)應(yīng)的反序列化過(guò)程中,則可以說(shuō)是生成的數(shù)據(jù)被還原成數(shù)據(jù)結(jié)構(gòu)或?qū)ο蟮倪^(guò)程。
這樣來(lái)說(shuō),數(shù)據(jù)序列化相當(dāng)于是將我們?cè)鹊膶?duì)象序列化概念做出了擴(kuò)展,在對(duì)象序列化和反序列化中,我們熟知的有兩種方法,其一是Java語(yǔ)言中提供的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ù)庫(kù)來(lái)實(shí)現(xiàn)數(shù)據(jù)的本地存儲(chǔ),從廣義上來(lái)說(shuō),這些都可以算做是數(shù)據(jù)的序列化
反序列化
將在序列化過(guò)程中所生成的二進(jìn)制串轉(zhuǎn)換成數(shù)據(jù)結(jié)構(gòu)或者對(duì)象的過(guò)程
數(shù)據(jù)結(jié)構(gòu)、對(duì)象與二進(jìn)制串
不同的計(jì)算機(jī)語(yǔ)言中,數(shù)據(jù)結(jié)構(gòu),對(duì)象以及二進(jìn)制串的表示方式并不相同。
數(shù)據(jù)結(jié)構(gòu)和對(duì)象:對(duì)于類似 Java 這種完全面向?qū)ο蟮恼Z(yǔ)言,工程師所操作的一切都是對(duì)象(Object),來(lái)自于類的實(shí)例化。在 Java 語(yǔ)言中最接近數(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ǔ)言的字符串可以直接被傳輸層使用,因?yàn)槠浔举|(zhì)上就是以'0'結(jié)尾的存儲(chǔ)在內(nèi)存中的二進(jìn)制串。在 Java 語(yǔ)言里面,二進(jìn)制串的概念容易和 String 混淆。實(shí)際上 String 是 Java 的一等公民,是一種特殊對(duì)象(Object)。對(duì)于跨語(yǔ)言間的通訊,序列化后的數(shù)據(jù)當(dāng)然不能是某種語(yǔ)言的特殊數(shù)據(jù)類型。二進(jìn)制串在 Java 里面所指的是 byte[],byte 是 Java 的 8 種原生數(shù)據(jù)類型之一(Primitive data types)。
序列化/反序列化的目的
簡(jiǎn)單的概括
- 序列化: 主要用于網(wǎng)絡(luò)傳輸,數(shù)據(jù)持久化,一般序列化也稱為編碼(Encode)
- 反序列化: 主要用于從網(wǎng)絡(luò),磁盤上讀取字節(jié)數(shù)組還原成原始對(duì)象,一般反序列化也稱為解碼(Decode)
具體的講: - 永久的保存對(duì)象數(shù)據(jù)(將對(duì)象數(shù)據(jù)保存在文件當(dāng)中,或者是磁盤中
- 通過(guò)序列化操作將對(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的生命周期更長(zhǎng)(即每個(gè)對(duì)象都在JVM中)但在現(xiàn)實(shí)應(yīng)用中,就可能要停止JVM運(yùn)行,但有要保存某些指定的對(duì)象,并在將來(lái)重新讀取被保存的對(duì)象。這是Java對(duì)象序列化就能夠?qū)崿F(xiàn)該功能。(可選擇入數(shù)據(jù)庫(kù)、或文件的形式保存)
- 序列化對(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)、跨語(yǔ)言。如果不支持,在技術(shù)層面上的通用性就大大降低了。
- 流行程度,序列化和反序列化需要多方參與,很少人使用的協(xié)議往往意味著昂貴的學(xué)習(xí)成本;另一方面,流行度低的協(xié)議,往往缺乏穩(wěn)定而成熟的跨語(yǔ)言、跨平臺(tái)的公共包。
強(qiáng)健性 / 魯棒性
- 成熟度夠不夠(是否經(jīng)過(guò)大量的使用測(cè)試,足夠的多版本的迭代優(yōu)化)
- 語(yǔ)言 / 平臺(tái)的不公平性
- 魯棒性:健壯性 是否能經(jīng)受的住各種使用,各種問(wèn)題是否考慮的足夠,能夠容錯(cuò)。
可調(diào)試性 / 可讀性
- 支持是否不到位
- 訪問(wèn)限制(Android 方面不用考慮)
性能
性能包括兩個(gè)方面,時(shí)間復(fù)雜度和空間復(fù)雜度。
- 空間開銷(Verbosity), 序列化需要在原有的數(shù)據(jù)上加上描述字段,以為反序列化解析之用。如果序列化過(guò)程引入的額外開銷過(guò)高,可能會(huì)導(dǎo)致過(guò)大的網(wǎng)絡(luò),磁盤等各方面的壓力。對(duì)于海量分布式存儲(chǔ)系統(tǒng),數(shù)據(jù)量往往以 TB 為單位,巨大的的額外空間開銷意味著高昂的成本。
- 時(shí)間開銷(Complexity),復(fù)雜的序列化協(xié)議會(huì)導(dǎo)致較長(zhǎng)的解析時(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)的靈活度。
安全性 / 訪問(wèn)限制
在序列化選型的過(guò)程中,安全性的考慮往往發(fā)生在跨局域網(wǎng)訪問(wèn)的場(chǎng)景。當(dāng)通訊發(fā)生在公司之間或者跨機(jī)房的時(shí)候,出于安全的考慮,對(duì)于跨局域網(wǎng)的訪問(wèn)往往被限制為基于 HTTP/HTTPS 的 80 和 443 端口。如果使用的序列化協(xié)議沒(méi)有兼容而成熟的 HTTP 傳輸層框架支持,可能會(huì)導(dǎo)致以下三種結(jié)果之一:
- 因?yàn)樵L問(wèn)限制而降低服務(wù)可用性;
- 被迫重新實(shí)現(xiàn)安全協(xié)議而導(dǎo)致實(shí)施成本大大提高;
- 開放更多的防火墻端口和協(xié)議訪問(wèn),而犧牲安全性
- 注意點(diǎn):Android的Parcelable也有安全漏洞 > 參考 https://www.anquanke.com/post/id/103570
幾種常見(jiàn)的序列化和反序列化協(xié)議
XML&SOAP
XML 是一種常用的序列化和反序列化協(xié)議,具有跨機(jī)器,跨語(yǔ)言等優(yōu)點(diǎn),SOAP(Simple Object Access protocol) 是一種被廣泛應(yīng)用的,基于 XML 為序列化和反序列化協(xié)議的結(jié)構(gòu)化消息傳遞協(xié)議
JSON(Javascript Object Notation)
JSON 起源于弱類型語(yǔ)言 Javascript, 它的產(chǎn)生來(lái)自于一種稱之為"Associative array"的概念,其本質(zhì)是就是采用"Attribute-value"的方式來(lái)描述對(duì)象。實(shí)際上在 Javascript 和 PHP 等弱類型語(yǔ)言中,類的描述方式就是 Associative array。JSON 的如下優(yōu)點(diǎn),使得它快速成為最廣泛使用的序列化協(xié)議之一。
- 這種 Associative array 格式非常符合工程師對(duì)對(duì)象的理解。
- 它保持了 XML 的人眼可讀(Human-readable)的優(yōu)點(diǎn)。
- 相對(duì)于 XML 而言,序列化后的數(shù)據(jù)更加簡(jiǎn)潔。 來(lái)自于的以下鏈接的研究表明:XML 所產(chǎn)生序列化之后文件的大小接近 JSON 的兩倍
- 它具備 Javascript 的先天性支持,所以被廣泛應(yīng)用于 Web browser 的應(yīng)用常景中,是 Ajax 的事實(shí)標(biāo)準(zhǔn)協(xié)議。
- 與 XML 相比,其協(xié)議比較簡(jiǎn)單,解析速度比較快。
- 松散的 Associative array 使得其具有良好的可擴(kuò)展性和兼容性
Protobuf
Protobuf 具備了優(yōu)秀的序列化協(xié)議的所需的眾多典型特征。
- 標(biāo)準(zhǔn)的 IDL 和 IDL 編譯器,這使得其對(duì)工程師非常友好。
- 序列化數(shù)據(jù)非常簡(jiǎn)潔,緊湊,與 XML 相比,其序列化之后的數(shù)據(jù)量約為 1/3 到 1/10。
- 解析速度非常快,比對(duì)應(yīng)的 XML 快約 20-100 倍。
- 提供了非常友好的動(dòng)態(tài)庫(kù),使用非常簡(jiǎn)介,反序列化只需要一行代碼。
Android應(yīng)該如何選擇序列化方案
Serializable接口
是 Java 提供的序列化接口,它是一個(gè)空接口:
public interface Serializable {
}
Serializable 用來(lái)標(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)無(wú)法被序列化/反序列化
- 也就是說(shuō),反序列化一個(gè)類的過(guò)程中,它的非可序列化的屬性將會(huì)調(diào)用無(wú)參構(gòu)造函數(shù)重新創(chuàng)建
- 因此這個(gè)屬性的無(wú)參構(gòu)造函數(shù)必須可以訪問(wèn),否者運(yùn)行時(shí)會(huì)報(bào)錯(cuò)
- 一個(gè)實(shí)現(xiàn)序列化的類,它的子類也是可序列化的
serialVersionUID與兼容性
- serialVersionUID的作用
serialVersionUID 用來(lái)表明類的不同版本間的兼容性。如果你修改了此類, 要修改此值。否則以前用老版本的類序列化的類恢復(fù)時(shí)會(huì)報(bào)錯(cuò): InvalidClassException - 設(shè)置方式
在JDK中,可以利用JDK的bin目錄下的serialver.exe工具產(chǎn)生這個(gè)serialVersionUID,對(duì)于Test.class,執(zhí)行命令:serialver Test - 兼容性問(wèn)題
為了在反序列化時(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)轭惏姹静患嫒荻 ?br> 不顯式定義這個(gè)屬性值的另一個(gè)壞處是,不利于程序在不同的JVM之間的移植。因?yàn)椴煌木幾g器實(shí)現(xiàn)該屬性值的計(jì)算策略可能不同,從而造成雖然類沒(méi)有改變,但是因?yàn)镴VM不同,出現(xiàn)因類版本不兼容而無(wú)法正確反序列化的現(xiàn)象出現(xiàn)
因此 JVM 規(guī)范強(qiáng)烈 建議我們手動(dòng)聲明一個(gè)版本號(hào),這個(gè)數(shù)字可以是隨機(jī)的,只要固定不變就可以。同時(shí)最好是 private 和 final 的,盡量保證不變。
序列化與反序列化 Serializable
Serializable 的序列化與反序列化分別通過(guò) 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;
}
Externalizable接口
public interface Externalizable extends Serializable {
void writeExternal(ObjectOutput var1) throws IOException;
void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException;
}
簡(jiǎn)單使用
public class Course1 implements Externalizable {
private static final long serialVersionUID = 667279791530738499L;
private String name;
private float score;
@Override
public String toString() {
return "Score{" +
"name='" + name + '\'' +
", score=" + 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 {
Course1 course = new Course1("英語(yǔ)", 12f);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(course);
byte[] bytes = out.toByteArray();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
Course1 course1 = (Course1) ois.readObject();
System.out.println("course1: " + course1);
}
}
重寫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的角度來(lái)講,是強(qiáng)烈建議我們重寫的,這樣有助于我們更好地把控> > 序列化過(guò)程,防范未知風(fēng)險(xiǎn)
package json;
import java.io.*;
public class Student implements Serializable {
private static final long serialVersionUID = 667279791530738499L;
private String name;
private float score;
public Student(String name, float score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getScore() {
return score;
}
public void setScore(float score) {
this.score = score;
}
private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException {
inputStream.defaultReadObject();
name = (String)inputStream.readObject();
score = inputStream.readFloat();
System.out.println("readObject:::::"+score);
}
private void writeObject(ObjectOutputStream outputStream) throws IOException {
System.out.println("writeObject:::::"+name);
outputStream.defaultWriteObject();
outputStream.writeObject(name);
outputStream.writeFloat(score);
}
private Object readResolve() {
System.out.println("readResolve");
return new Student(name, 85f);
}
private Object writeReplace(){
System.out.println("writeReplace");
return new Student(name +"replace",score);
}
public static void main(String... args) throws Exception {
Student student = new Student("英語(yǔ)", 12f);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(student);
byte[] bs = out.toByteArray();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bs));
Student student1 = (Student) ois.readObject();
System.out.println("course1: " + student1);
}
@Override
public String toString() {
return "Asfas{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
輸出結(jié)果:
Task :Student.main()
writeReplace
writeObject:::::英語(yǔ)replace
readObject:::::12.0
readResolve
course1: Asfas{name='英語(yǔ)replace', score=85.0}
單例模式的序列化問(wèn)題/反射問(wèn)題
如果需要序列化單例模式就需要 單例模式實(shí)現(xiàn) Serializable 接口 然后實(shí)現(xiàn)readResolve方法 不是的話就會(huì)反序列化失敗
//如果不重寫readResolve,會(huì)導(dǎo)致單例模式在序列化->反序列化后失敗
private Object readResolve() {
return single;
}
Parcelable接口
介紹Parcelable不得不先提一下Serializable接口,Serializable是Java為我們提供的一個(gè)標(biāo)準(zhǔn)化的序列化接口,那什么是序列化呢? ---- 簡(jiǎn)單來(lái)說(shuō)就是將對(duì)象轉(zhuǎn)換為可以傳輸?shù)亩M(jìn)制流(二進(jìn)制序列)的過(guò)程,這樣我們就可以通過(guò)序列化,轉(zhuǎn)化為可以在網(wǎng)絡(luò)傳輸或者保存到本地的流(序列),從而進(jìn)行傳輸數(shù)據(jù) ,那反序列化就是從二進(jìn)制流(序列)轉(zhuǎn)化為對(duì)象的過(guò)程.
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
public class Student implements Parcelable {
public String name;
public float score;
/**
* 描述當(dāng)前 Parcelable 實(shí)例的對(duì)象類型
* 比如說(shuō),如果對(duì)象中有文件描述符,這個(gè)方法就會(huì)返回Parcelable 中的 CONTENTS_FILE_DESCRIPTOR
* 其他情況會(huì)返回一個(gè)位掩碼
* @return int
*/
@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);
}
public Student() {
}
protected Student(Parcel in) {
this.name = in.readString();
this.score = in.readFloat();
}
/**
* 實(shí)現(xiàn)類必須有一個(gè) Creator 屬性,用于反序列化,將 Parcel 對(duì)象轉(zhuǎn)換為 Parcelable
*/
public static final Parcelable.Creator<Student> CREATOR = new Parcelable.Creator<Student>() {
//反序列化的方法,將Parcel還原成Java對(duì)象
@Override
public Student createFromParcel(Parcel source) {
return new Student(source);
}
//提供給外部類反序列化這個(gè)數(shù)組使用。
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
}
在介紹之前我們需要先了解Parcel是什么?Parcel翻譯過(guò)來(lái)是打包的意思,其實(shí)就是包裝了我們需要傳輸?shù)臄?shù)據(jù),然后在Binder中傳輸,也就是用于跨進(jìn)程傳輸數(shù)據(jù)
簡(jiǎn)單來(lái)說(shuō),Parcel提供了一套機(jī)制,可以將序列化之后的數(shù)據(jù)寫入到一個(gè)共享內(nèi)存中,其他進(jìn)程通過(guò)Parcel可以從這塊共享內(nèi)存中讀出字節(jié)流,并反序列化成對(duì)象,下圖是這個(gè)過(guò)程的模型。

Parcel可以包含原始數(shù)據(jù)類型(用各種對(duì)應(yīng)的方法寫入,比如writeInt(),writeFloat()等),可以包含Parcelable對(duì)象,它還包含了一個(gè)活動(dòng)的IBinder對(duì)象的引用,這個(gè)引用導(dǎo)致另一端接收到一個(gè)指向這個(gè)IBinder的代理IBinder。
Parcelable通過(guò)Parcel實(shí)現(xiàn)了read和write的方法,從而實(shí)現(xiàn)序列化和反序列化。
Parcelable與Serializable的性能比較
-
Serializable性能分析:
Serializable是Java中的序列化接口,其使用起來(lái)簡(jiǎn)單但開銷較大(因?yàn)镾erializable在序列化過(guò)程中使用了反射機(jī)制,故而會(huì)產(chǎn)生大量的臨時(shí)變量,從而導(dǎo)致頻繁的GC),并且在讀寫數(shù)據(jù)過(guò)程中,它是通過(guò)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)勢(shì),那么自然就要優(yōu)先選擇.
- 在讀寫數(shù)據(jù)的時(shí)候,Parcelable是在內(nèi)存中直接進(jìn)行讀寫,而Serializable是通過(guò)使用IO流的形式將數(shù)據(jù)讀寫入在硬盤上.
但是:雖然Parcelable的性能要強(qiáng)于Serializable,但是仍然有特殊的情況需要使用Serializable,而不去使用Parcelable,因?yàn)镻arcelable無(wú)法將數(shù)據(jù)進(jìn)行持久化,因此在將數(shù)據(jù)保存在磁盤的時(shí)候,仍然需要使用后者,因?yàn)榍罢邿o(wú)法很好的將數(shù)據(jù)進(jìn)行持久化.(原因是在不同的Android版本當(dāng)中,Parcelable可能會(huì)不同,因此數(shù)據(jù)的持久化方面仍然是使用Serializable)
幾個(gè)面試相關(guān)的問(wèn)題
-
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的場(chǎng)景大多數(shù)為小數(shù)據(jù)量,我沒(méi)見(jiàn)過(guò)在兩個(gè)Activity之間傳遞10個(gè)以上數(shù)據(jù)的場(chǎng)景,所以相比之下,在這種情況下使用ArrayMap保存數(shù)據(jù),在操作速度和內(nèi)存占用上都具有優(yōu)勢(shì),因此使用Bundle來(lái)傳遞數(shù)據(jù),可以保證更快的速度和更少的內(nèi)存占用。
另外一個(gè)原因,則是在Android中如果使用Intent來(lái)攜帶數(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ū)就更小了( 有人以前做過(guò)測(cè)試,大約一個(gè)線程可以占用 128 KB)。所以當(dāng)你看到 The Binder transaction failed because it was too large 這類 TransactionTooLargeException 異常時(shí),你應(yīng)該知道怎么解決了 -
為何Intent不能直接在組件間傳遞對(duì)象而要通過(guò)序列化機(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ì)象是無(wú)法傳輸,所以我們此處要對(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)程間通信等的接口start_activity_process.jpg
