數(shù)據(jù)傳輸效率優(yōu)化
一、數(shù)據(jù)的序列化和反序列化
服務(wù)器對(duì)象Object------數(shù)據(jù)流---->客戶端Object對(duì)象
傳統(tǒng)序列化:
Serializable/Parcelable
效率低
像新聞端用戶瀏覽時(shí)會(huì)下載大量圖片和文字
采用傳統(tǒng)數(shù)據(jù)傳輸會(huì)造成內(nèi)存的浪費(fèi)和CPU計(jì)算時(shí)間的占用
數(shù)據(jù)的序列化是程序代碼里面必不可少的組成部分,當(dāng)我們討論到數(shù)據(jù)序列化的性能的時(shí)候,需要了解有哪些候選的方案,他們各自的優(yōu)缺點(diǎn)是什么。首先什么是序列化?用下面的圖來(lái)解釋一下:

數(shù)據(jù)序列化的行為可能發(fā)生在數(shù)據(jù)傳遞過(guò)程中的任何階段,例如網(wǎng)絡(luò)傳輸,不同進(jìn)程間數(shù)據(jù)傳遞,不同類(lèi)之間的參數(shù)傳遞,把數(shù)據(jù)存儲(chǔ)到磁盤(pán)上等等。通常情況下,我們會(huì)把那些需要序列化的類(lèi)實(shí)現(xiàn)Serializable接口(如下圖所示),但是這種傳統(tǒng)的做法效率不高,實(shí)施的過(guò)程會(huì)消耗更多的內(nèi)存。

但是我們?nèi)绻褂肎SON庫(kù)來(lái)處理這個(gè)序列化的問(wèn)題,不僅僅執(zhí)行速度更快,內(nèi)存的使用效率也更高。Android的XML布局文件會(huì)在編譯的階段被轉(zhuǎn)換成更加復(fù)雜的格式,具備更加高效的執(zhí)行性能與更高的內(nèi)存使用效率。

下面介紹三個(gè)數(shù)據(jù)序列化的候選方案:
- Protocal Buffers:強(qiáng)大,靈活,但是對(duì)內(nèi)存的消耗會(huì)比較大,并不是移動(dòng)終端上的最佳選擇。
- Nano-Proto-Buffers:基于Protocal,為移動(dòng)終端做了特殊的優(yōu)化,代碼執(zhí)行效率更高,內(nèi)存使用效率更佳。
- FlatBuffers:這個(gè)開(kāi)源庫(kù)最開(kāi)始是由Google研發(fā)的,專(zhuān)注于提供更優(yōu)秀的性能。
上面這些方案在性能方面的數(shù)據(jù)對(duì)比如下圖所示:


可見(jiàn),F(xiàn)latBuffers 幾乎從空間和時(shí)間復(fù)雜度上完勝其他技術(shù)。
FlatBuffers 是一個(gè)開(kāi)源的跨平臺(tái)數(shù)據(jù)序列化庫(kù),可以應(yīng)用到幾乎任何語(yǔ)言(C++, C#, Go, Java, JavaScript, PHP, Python),最開(kāi)始是 Google 為游戲或者其他對(duì)性能要求很高的應(yīng)用開(kāi)發(fā)的。
項(xiàng)目地址在GitHub 上。官方的文檔地址。
FlatBuffer 的優(yōu)點(diǎn)
FlatBuffer 相對(duì)于其他序列化技術(shù),例如 XML,JSON,Protocol Buffers 等,有哪些優(yōu)勢(shì)呢?官方文檔的說(shuō)法如下:
1.直接讀取序列化數(shù)據(jù),而不需要解析(Parsing)或者解包(Unpacking):FlatBuffer 把數(shù)據(jù)層級(jí)結(jié)構(gòu)保存在一個(gè)扁平化的二進(jìn)制緩存(一維數(shù)組)中,同時(shí)能夠保持直接獲取里面的結(jié)構(gòu)化數(shù)據(jù),而不需要解析,并且還能保證數(shù)據(jù)結(jié)構(gòu)變化的前后向兼容。
2.高效的內(nèi)存使用和速度:FlatBuffer 使用過(guò)程中,不需要額外的內(nèi)存,幾乎接近原始數(shù)據(jù)在內(nèi)存中的大小。
3.靈活:數(shù)據(jù)能夠前后向兼容,并且能夠靈活控制你的數(shù)據(jù)結(jié)構(gòu)。
4.很少的代碼侵入性:使用少量的自動(dòng)生成的代碼即可實(shí)現(xiàn)。
5.強(qiáng)數(shù)據(jù)類(lèi)性,易于使用,跨平臺(tái),幾乎語(yǔ)言無(wú)關(guān)。
官方提供了一個(gè)性能對(duì)比表如下:

在做 Android 開(kāi)發(fā)的時(shí)候,JSON 是最常用的數(shù)據(jù)序列化技術(shù)。我們知道,JSON 的可讀性很強(qiáng),但是序列化和反序列化性能卻是最差的。解析的時(shí)候,JSON 解析器首先,需要在內(nèi)存中初始化一個(gè)對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),這個(gè)事件經(jīng)常會(huì)消耗 100ms ~ 200ms2;解析過(guò)程中,要產(chǎn)生大量的臨時(shí)變量,造成 Java 虛擬機(jī)的 GC 和內(nèi)存抖動(dòng),解析 20KB 的數(shù)據(jù),大概會(huì)消耗 100KB 的臨時(shí)內(nèi)存2。FlatBuffers 就解決了這些問(wèn)題。
FlatBuffer 使用
下載文件編譯工具1.下載源碼$ git clone https://github.com/google/flatbuffers2.下載編譯工具https://cmake.org/download/ 下載對(duì)應(yīng)的cmake工具
參考網(wǎng)上:可以使用 cmake編譯成flatc工具。但是暫時(shí)沒(méi)有搞懂怎么編譯?
最后,在這個(gè)地方可以下載windows的 exe可以運(yùn)用文件。https://github.com/google/flatbuffers/releases比較坑的最新的1.7.1沒(méi)有exe沒(méi)有exe下載。也沒(méi)有看后面的,以為需要自己編譯。最后一不留 神看到1.7.0有下載。哎。。。。。。。。
編寫(xiě)描述使用 FlatBuffers 的 IDL 定義好數(shù)據(jù)結(jié)構(gòu) Schema,編寫(xiě) Schema 的詳細(xì)文檔在這里 http://google.github.io/flatbuffers/flatbuffers_guide_writing_schema.html。參考一個(gè)例子
namespace com.haocai.app.flatbuffer;
table Items {
ItemId : long;
timestemp : int;
basic:[Basic];
}
table Basic{
id:int;
name:string;
email:int;
code:long;
isVip:bool;
count:int;
carList:[Car];
}
table Car{
id:long;
number:long;
describle:string;
}
root_type Items;
使用工具 生產(chǎn)相應(yīng)數(shù)據(jù)類(lèi)

2.根據(jù)json生成和fbs 生成flatbuffer 二進(jìn)制文件.bin
例如repos_json.json (有數(shù)據(jù)),對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)repos_schema.fbs ,生成repos_json.bin(二進(jìn)制flatbuffer格式的)
$ ./flatc -j -b repos_schema.fbs repos_json.json
工程中使用
編寫(xiě)對(duì)應(yīng)描述文件,翻譯成對(duì)應(yīng)語(yǔ)言的類(lèi)。把對(duì)應(yīng)的類(lèi), 放到你的工程中。

調(diào)用
MainActivity.Java
public class MainActivity extends AppCompatActivity {
private static final String TAG ="main" ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void serialize(View v) {
//----------------序列化-------------
FlatBufferBuilder builder = new FlatBufferBuilder();
int id1 = builder.createString("蘭博基尼");
//準(zhǔn)備Car對(duì)象
int car1 = Car.createCar(builder,10001L,88888L,id1);
int id2 = builder.createString("奧迪A8");
//準(zhǔn)備Car對(duì)象
int car2 = Car.createCar(builder,10001L,88888L,id2);
int id3 = builder.createString("奧迪A9");
//準(zhǔn)備Car對(duì)象
int car3 = Car.createCar(builder,10001L,88888L,id3);
int[] cars = new int[3];
cars[0] = car1;
cars[1] = car2;
cars[2] = car3;
//創(chuàng)建Basic對(duì)象里面的Car集合
int carList = Basic.createCarListVector(builder,cars);
int name = builder.createString("kpioneer");
int email = builder.createString("kpioneer@qq.com");
int basic = Basic.createBasic(builder,10,name,email,100L,true,100,carList);
int basicOffset = Items.createBasicVector(builder,new int[]{basic});
/**
* table Items {
ItemId : long;
timestemp : int;
basic:[Basic];
}
*/
Items.startItems(builder);
Items.addItemId(builder,1000L);
Items.addTimestemp(builder,2016);
Items.addBasic(builder,basicOffset);
int rootItems = Items.endItems(builder);
Items.finishItemsBuffer(builder,rootItems);
//============保存數(shù)據(jù)到文件=================
File sdcard = Environment.getExternalStorageDirectory();
//保存的路徑
File file = new File(sdcard,"Items.txt");
if(file.exists()){
file.delete();
}
ByteBuffer data = builder.dataBuffer();
FileOutputStream out = null;
FileChannel channel = null;
try {
out = new FileOutputStream(file);
channel = out.getChannel();
while(data.hasRemaining()){
channel.write(data);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(out!=null){
out.close();
}
if(channel!=null){
channel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//===================反序列化=============================
FileInputStream fis = null;
FileChannel readChannel = null;
try {
fis = new FileInputStream(file);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
readChannel = fis.getChannel();
int readBytes = 0;
while ((readBytes=readChannel.read(byteBuffer))!=-1){
System.out.println("讀取數(shù)據(jù)個(gè)數(shù):"+readBytes);
}
//把指針回到最初的狀態(tài),準(zhǔn)備從byteBuffer當(dāng)中讀取數(shù)據(jù)
byteBuffer.flip();
//解析出二進(jìn)制為Items對(duì)象。
Items items = Items.getRootAsItems(byteBuffer);
//讀取數(shù)據(jù)測(cè)試看看是否跟保存的一致
Log.i(TAG,"items.id:"+items.ItemId());
Log.i(TAG,"items.timestemp:"+items.timestemp());
Basic basic2 = items.basic(0);
Log.i(TAG,"basic2.name:"+basic2.name());
Log.i(TAG,"basic2.email:"+basic2.email());
//carList
int length = basic2.carListLength();
for (int i=0;i<length; i++){
Car car = basic2.carList(i);
Log.i(TAG,"car.number:"+car.number());
Log.i(TAG,"car.describle:"+car.describle());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(readChannel!=null){
readChannel.close();
}
if(fis!=null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
結(jié)果輸出:
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/System.out: 讀取數(shù)據(jù)個(gè)數(shù):304
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: items.id:1000
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: items.timestemp:2016
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: basic2.name:kpioneer
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: basic2.email:196
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: car.number:88888
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: car.describle:蘭博基尼
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: car.number:88888
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: car.describle:奧迪A8
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: car.number:88888
09-27 16:25:38.567 15008-15008/com.haocai.app.flatbuffer I/main: car.describle:奧迪A9
基本原理

在上面的布局中,你需要注意:
每個(gè)對(duì)象都被分為兩個(gè)部分:元數(shù)據(jù)的部分(或vtable)在軸心點(diǎn)的左邊,真實(shí)的數(shù)據(jù)部分在右邊。
每個(gè)字段對(duì)應(yīng)于vtable中的一個(gè)槽,其中存儲(chǔ)了那個(gè)字段的真實(shí)數(shù)據(jù)的偏移量。比如,John的table的第一個(gè)槽的值為1,表明了John的名字被存放在了距離Jonh的軸心點(diǎn)向右偏移一個(gè)字節(jié)的地方。
對(duì)于對(duì)象字段,vtable中的偏移量指向子對(duì)象的軸心點(diǎn)。比如,John的vtable中的第三個(gè)槽指向了Mary的軸心點(diǎn)。
要表示字段沒(méi)有值,我們可以在一個(gè)vtable槽中使用一個(gè)0偏移量。

特別感謝: