自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入淺出 FlatBuffers 原理

開發(fā)
FlatBuffers 是一個開源的、跨平臺的、高效的、提供了多種語言接口的序列化工具庫。實現(xiàn)了與 Protocal Buffers 類似的序列化格式。主要由 Wouter van Oortmerssen 編寫,并由 Google 開源。Oortmerssen 最初為 Android 游戲和注重性能的應(yīng)用而開發(fā)了 FlatBuffers,現(xiàn)在它具有 C ++、C#、C、Go、Java、PHP、Python 和 JavaScript 的接口。

 [[412244]]

一 前言

FlatBuffers 是一個開源的、跨平臺的、高效的、提供了多種語言接口的序列化工具庫。實現(xiàn)了與 Protocal Buffers 類似的序列化格式。主要由 Wouter van Oortmerssen 編寫,并由 Google 開源。Oortmerssen 最初為 Android 游戲和注重性能的應(yīng)用而開發(fā)了 FlatBuffers,現(xiàn)在它具有 C ++、C#、C、Go、Java、PHP、Python 和 JavaScript 的接口。

高德地圖數(shù)據(jù)編譯增量發(fā)布使用了FlatBuffers序列化工具,借此契機(jī)對FlatBuffers原理進(jìn)行研究并分享于此。本文簡單介紹 FlatBuffers Scheme,通過剖析 FlatBuffers 序列化與反序列化原理,重點(diǎn)回答以下問題:

問題1:FlatBuffers 如何做到反序列化速度極快的(或者說無需解碼)。
問題2:FlatBuffers 如何做到默認(rèn)值不占存儲空間的(Table 結(jié)構(gòu)內(nèi)的變量)。
問題3:FlatBuffers 如何做到字節(jié)對齊的。
問題4:FlatBuffers 如何做到向前向后兼容的(Struct 結(jié)構(gòu)除外)。
問題5:FlatBuffers 在 add 字段時有沒有順序要求(Table 結(jié)構(gòu))。
問題6:FlatBuffers 如何根據(jù) Scheme 自動生成編解碼器。
問題7:FlatBuffers 如何根據(jù) Scheme 自動生成 Json。

二 FlatBuffers Scheme

FlatBuffers 通過 Scheme 文件定義數(shù)據(jù)結(jié)構(gòu),Schema 定義與其他框架使用的IDL(Interface description language)語言類似簡單易懂,F(xiàn)latBuffers 的 Scheme 是一種類 C 的語言(盡管 FlatBuffers 有自己的接口定義語言 Scheme 來定義要與之序列化的數(shù)據(jù),但它也支持 Protocol Buffers 中的 .proto 格式)。下面以官方 Tutorial 中的 monster.fbs 為例進(jìn)行說明:

// Example IDL file for our monster's schema.namespace MyGame.Sample;enum Color:byte { Red = 0, Green, Blue = 2 }union Equipment { Weapon } // Optionally add more tables.struct Vec3 { x:float; y:float; z:float;}table Monster { pos:Vec3; mana:short = 150; hp:short = 100; name:string; friendly:bool = false (deprecated); inventory:[ubyte]; color:Color = Blue; weapons:[Weapon]; equipped:Equipment; path:[Vec3];}table Weapon { name:string; damage:short;}root_type Monster;
namespace MyGame.Sample;

namespace 定義命名空間,可以定義嵌套的命名空間,用 . 分割。

enum Color:byte { Red = 0, Green, Blue = 2 };

enum 定義枚舉類型。和常規(guī)的枚舉類稍有不同的地方是可以定義類型。比如這里的 Color 是 byte 類型。enum 字段只能新增,不能廢棄。

union Equipment {Weapon} // Optionally add more tables

union 類似 C/C++ 中的概念,一個 union 中可以放置多種類型,共同使用一個內(nèi)存區(qū)域。這里的使用是互斥的,即這塊內(nèi)存區(qū)域只能由其中一種類型使用。相對 struct 來說比較節(jié)省內(nèi)存。union 跟 enum 比較類似,但是 union 包含的是 table,enum 包含的是 scalar 或者 struct。union 也只能作為 table 的一部分,不能作 root type。

struct Vect3{ x : float; y : float; z : float;};

struct 所有字段都是必填的,因此沒有默認(rèn)值。字段也不能添加或者廢棄,且只能包含標(biāo)量或者其他 struct。struct 主要用于數(shù)據(jù)結(jié)構(gòu)不會發(fā)生改變的場景,相對 table 使用更少的內(nèi)存,lookup 的時候速度更快(struct 保存在父 table 中,不需要使用 vtable)。

table Monster{};

table 是在 FlatBuffers 中定義對象的主要方式,由一個名稱(這里是 Monster)和一個字段列表組成??梢园厦娑x的所有類型。每個字段(Field)包括名稱、類型和默認(rèn)值三部分;每個字段都有默認(rèn)值,如果沒有明確寫出則默認(rèn)為 0 或者 null。每個字段都不是必須的,可以為每個對象選擇要省略的字段,這是 FlatBuffers 向前和向后兼容的機(jī)制。

root_type Monster;

用于指定序列化后的數(shù)據(jù)的 root table。

Scheme 設(shè)計需要特別注意的:

新字段只能加在 table 的后面。舊代碼會忽略這個字段,仍然可以正常執(zhí)行。新代碼讀取舊的數(shù)據(jù),會取到新增字段的默認(rèn)值。
即使字段不再使用了也不能從 Scheme 中刪除??梢詷?biāo)記為 deprecated,在生成代碼的時候不會生成該字段的訪問器。
如果需要嵌套的 vector,可以將 vector 包裝在 table 中。string 對于其他編碼可以使用 [byte] 或者 [ubyte] 支持。

三 FlatBuffers 的序列化

簡單來說 FlatBuffers 就是把對象數(shù)據(jù),保存在一個一維的數(shù)組中,將數(shù)據(jù)都緩存在一個 ByteBuffer 中,每個對象在數(shù)組中被分為兩部分。元數(shù)據(jù)部分:負(fù)責(zé)存放索引。真實數(shù)據(jù)部分:存放實際的值。然而 FlatBuffers 與大多數(shù)內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)不同,它使用嚴(yán)格的對齊規(guī)則和字節(jié)順序來確保 buffer 是跨平臺的。此外,對于 table 對象,F(xiàn)latBuffers 提供前向/后向兼容性和 optional 字段,以支持大多數(shù)格式的演變。除了解析效率以外,二進(jìn)制格式還帶來了另一個優(yōu)勢,數(shù)據(jù)的二進(jìn)制表示通常更具有效率。我們可以使用 4 字節(jié)的 UInt 而不是 10 個字符來存儲 10 位數(shù)字的整數(shù)。

FlatBuffers 對序列化基本使用原則:

小端模式。FlatBuffers 對各種基本數(shù)據(jù)的存儲都是按照小端模式來進(jìn)行的,因為這種模式目前和大部分處理器的存儲模式是一致的,可以加快數(shù)據(jù)讀寫的數(shù)據(jù)。
寫入數(shù)據(jù)方向和讀取數(shù)據(jù)方向不同。

FlatBuffers 向 ByteBuffer 中寫入數(shù)據(jù)的順序是從 ByteBuffer 的尾部向頭部填充,由于這種增長方向和 ByteBuffer 默認(rèn)的增長方向不同,因此 FlatBuffers 在向 ByteBuffer 中寫入數(shù)據(jù)的時候就不能依賴 ByteBuffer 的 position 來標(biāo)記有效數(shù)據(jù)位置,而是自己維護(hù)了一個 space 變量來指明有效數(shù)據(jù)的位置,在分析 FlatBuffersBuilder 的時候要特別注意這個變量的增長特點(diǎn)。但是,和數(shù)據(jù)的寫入方向不同的是,F(xiàn)latBuffers 從 ByteBuffer 中解析數(shù)據(jù)的時候又是按照 ByteBuffer 正常的順序來進(jìn)行的。FlatBuffers 這樣組織數(shù)據(jù)存儲的好處是,在從左到右解析數(shù)據(jù)的時候,能夠保證最先讀取到的就是整個 ByteBuffer 的概要信息(例如 Table 類型的 vtable 字段),方便解析。

對于每種數(shù)據(jù)類型的序列化:

1 標(biāo)量類型

標(biāo)量類型即基本類型,如:int,double,bool等,標(biāo)量類型使用直接尋址進(jìn)行數(shù)據(jù)訪問。

示例:short mana = 150; 12個字節(jié),存儲結(jié)構(gòu)如下:

schema 中定義標(biāo)量可以設(shè)置默認(rèn)值。文章最初提到 FlatBuffers 的默認(rèn)值不占存儲空間的,對于 table 內(nèi)部的標(biāo)量,是可以做到默認(rèn)值不存儲的,如果變量的值不需要改變,該字段在 vtable 中對應(yīng)的 offset 的值設(shè)置為 0 即可,默認(rèn)值被記錄在解碼接口內(nèi),解碼時獲取該字段的 offset 為 0 時,解碼接口則返回默認(rèn)值。對于 struct 結(jié)構(gòu)因為沒有使用 vtable 結(jié)構(gòu),因此內(nèi)部的標(biāo)量沒有默認(rèn)值,必須存儲(struct 類型和 table 類型的序列化原理在下文會詳細(xì)說明)。

  1. // Computes how many bytes you'd have to pad to be able to write an// "scalar_size" scalar if the buffer had grown to "buf_size" (downwards in// memory).inline size_t PaddingBytes(size_t buf_size, size_t scalar_size) {    return ((~buf_size) + 1) & (scalar_size - 1);} 

標(biāo)量數(shù)據(jù)類型是按其本身字節(jié)數(shù)大小進(jìn)行對齊。通過 PaddingBytes 函數(shù)計算,所有標(biāo)量都會調(diào)用這個函數(shù),進(jìn)行字節(jié)對齊。

2 Struct 類型

除了基本類型之外,F(xiàn)latBuffers 中只有 Struct 類型使用直接尋址進(jìn)行數(shù)據(jù)訪問。FlatBuffers 規(guī)定 Struct 類型用于存儲那些約定成俗、永不改變的數(shù)據(jù),這種類型的數(shù)據(jù)結(jié)構(gòu)一旦確定便永遠(yuǎn)不會改變,沒有任何字段是可選的(也沒有默認(rèn)值),字段可能不會被添加或被棄用,所以 structs 不提供前向/后向兼容性。在這個規(guī)定之下,為了提高數(shù)據(jù)訪問速度,F(xiàn)latBuffers 單獨(dú)對 Struct 使用了直接尋址的方式。字段的順序即為存儲的順序。struct 有的特性一般不作為 schema 文件的根。

示例:struct Vec3(16, 17, 18); 12個字節(jié)

struct 定義了一個固定的內(nèi)存布局,其中所有字段都與其大小對齊,并且 struct 與其最大標(biāo)量成員對齊。

3 vector 類型

vector 類型實際上就是 schema 中聲明的數(shù)組類型,F(xiàn)latBuffers 中也沒有單獨(dú)的類型和它對應(yīng),但是它卻有自己獨(dú)立的一套存儲結(jié)構(gòu),在序列化數(shù)據(jù)時先會從高位到低位依次存儲 vector 內(nèi)部的數(shù)據(jù),然后再在數(shù)據(jù)序列化完畢后寫入 Vector 的成員個數(shù)。數(shù)據(jù)存儲結(jié)構(gòu)如下:

示例:byte[] treasure = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

vector size 的類型為 int,因此在初始化申請內(nèi)存時 vector 進(jìn)行四字節(jié)字節(jié)對齊。

4 String 類型

FlatBuffers 字符串按照 utf-8 的方式進(jìn)行了編碼,在實現(xiàn)字符串寫入的時候?qū)⒆址木幋a數(shù)組當(dāng)做了一維的 vector 來實現(xiàn)。string 本質(zhì)上也可以看做是 byte 的 vector ,因此創(chuàng)建過程和 vector 基本一致,唯一的區(qū)別就是字符串是以null結(jié)尾,即最后一位是 0。string 寫入數(shù)據(jù)的結(jié)構(gòu)如下:

示例:string name = “Sword”;

vector size 的類型為 int,因此在初始化申請內(nèi)存時字符串進(jìn)行四字節(jié)字節(jié)對齊。

5 Union 類型

Union 類型比較特殊,F(xiàn)latBuffers 規(guī)定這個類型在使用上具有如下兩個限制:

Union 類型的成員只能是 Table 類型。
Union 類型不能是一個 schema 文件的根。
FlatBuffers 中沒有特定類型表示 union,而是會生成一個單獨(dú)的類對應(yīng) union 的成員類型。與其他類型的主要區(qū)別是需要先指定類型,在序列化 Union 的時候一般先寫入 Union 的 type,然后再寫入 Union 的數(shù)據(jù)偏移;在反序列化 Union 的時候一般先
析出 Union 的 type,然后再按照 type 對應(yīng)的 Table 類型來解析 Union 對應(yīng)的數(shù)據(jù)。

6 Enum 類型

FlatBuffers 中的 enum 類型在數(shù)據(jù)存儲的時候是和 byte 類型存儲的方式一樣的。因為和 Union 類型相似,enum 類型在 FlatBuffers 中也沒有單獨(dú)的類與它對應(yīng),在 schema 中聲明為 enum 的類會被編譯生成單獨(dú)的類。

enum 類型不能是一個 schema 文件的根。

7 Table 類型

table 是 FlatBuffers 的基石,為了解決數(shù)據(jù)結(jié)構(gòu)變更的問題,table 通過 vtable 間接訪問字段。每個 table 都帶有一個 vtable(可以在具有相同布局的多個 table 之間共享),并且包含存儲此特定類型 vtable 實例的字段的信息。vtable 還可能表明該字段不存在(因為此 FlatBuffers 是使用舊版本的代碼編寫的,僅僅因為信息對于此實例不是必需的,或者被視為已棄用),在這種情況下會返回默認(rèn)值。

table 的內(nèi)存開銷很?。ㄒ驗?vtables 很小并且共享)訪問成本也很小(間接訪問),但是提供了很大的靈活性。table 在特殊情況下可能比等價的 struct 花費(fèi)更少的內(nèi)存,因為字段在等于默認(rèn)值時不需要存儲在 buffer 中。這樣的結(jié)構(gòu)決定了一些復(fù)雜類型的成員都是使用相對尋址進(jìn)行數(shù)據(jù)訪問的,即先從Table 中取到成員常量的偏移,然后根據(jù)這個偏移再去常量真正存儲的地址去取真實數(shù)據(jù)。

單就結(jié)構(gòu)來講:首先可以將 Table 分為兩個部分,第一部分是存儲 Table 中各個成員變量的概要,這里命名為 vtable,第二部分是 Table 的數(shù)據(jù)部分,存儲 Table 中各個成員的值,這里命名為 table_data。注意 Table 中的成員如果是簡單類型或者 Struct 類型,那么這個成員的具體數(shù)值就直接存儲在 table_data中;如果成員是復(fù)雜類型,那么 table_data 中存儲的只是這個成員數(shù)據(jù)相對于寫入地址的偏移,也就是說要獲得這個成員的真正數(shù)據(jù)還要取出 table_data 中的數(shù)據(jù)進(jìn)行一次相對尋址。

vtable 是一個 short 類型的數(shù)組,其長度為(字段個數(shù)+2)*2字節(jié),第一個字段是 vtable 的大小,包括這個大小本身;第二個字段是 vtable 對應(yīng)的對象的大小,包括到 vtable 的 offset;接下來是每個字段相對于對象開始位置的 offset。
table_data 的開頭是 vtable 開始位置減去當(dāng)前table對象開始位置的 INT 型 offset,由于 vtable 可能在任意的地方,這個值有可能是負(fù)值。table_data開始用int存儲了vtable的offset,因此進(jìn)行了四字節(jié)對齊的。
add 的操作是添加 table_data,由于 Table 數(shù)據(jù)結(jié)構(gòu)的是通過 vtable - table_data 機(jī)制存儲的,這個操作沒有強(qiáng)制要求字段的先后順序,對順序沒有要求,因為vtable在記錄每個字段相對于對象開始位置的 offset 時是按照 schema 中定義的順序進(jìn)行存儲的,所以在add字段的時候即使沒有順序也可以根據(jù) offset 獲取正確的值。需要注意的是,每次add字段時 FlatBuffers 都會做字節(jié)對齊處理。

  1. std::string e_poiId = "1234567890";double e_coord_x = 0.1double e_coord_y = 0.2;int e_minZoom = 10;int e_maxZoom = 200;//addfeatureBuilder.add_poiId(nameData);featureBuilder.add_x(e_coord_x);featureBuilder.add_y(e_coord_y);featureBuilder.add_maxZoom(e_maxZoom);featureBuilder.add_minZoom(e_minZoom);auto rootData = featurePoiBuilder.Finish();flatBufferBuilder.Finish(rootData);blob = flatBufferBuilder.GetBufferPointer();blobSize = flatBufferBuilder.GetSize(); 

add 順序 1:最終二進(jìn)制的大小為 72 字節(jié)。

  1. std::string e_poiId = "1234567890";double e_coord_x = 0.1double e_coord_y = 0.2;int e_minZoom = 10;int e_maxZoom = 200;//addfeatureBuilder.add_poiId(nameData);featureBuilder.add_x(e_coord_x);featureBuilder.add_minZoom(e_minZoom);featureBuilder.add_y(e_coord_y);featureBuilder.add_maxZoom(e_maxZoom);auto rootData = featurePoiBuilder.Finish();flatBufferBuilder.Finish(rootData);blob = flatBufferBuilder.GetBufferPointer();blobSize = flatBufferBuilder.GetSize(); 

add 順序 2:最終二進(jìn)制的大小為 80 字節(jié)。

add 順序 1 和 add 順序 2 對應(yīng)的 schema 文件一樣,表達(dá)的數(shù)據(jù)也一樣,Table 結(jié)構(gòu)在 add 字段時有沒有順序要求。序列化后的數(shù)據(jù)大小差8個字節(jié),原因就是字節(jié)對齊導(dǎo)致的。因此 add 字段的時候,盡量把相同類型的字段放在一起進(jìn)行 add,這樣會避免不必要的字節(jié)對齊,獲取更小的序列化結(jié)果。

FlatBuffers 的向前向后兼容指的是 table 結(jié)構(gòu)。table 結(jié)構(gòu)每個字段都有默認(rèn)值,如果沒有明確寫出則默認(rèn)為 0 或者 null。每個字段都不是必須的,可以為每個對象選擇要省略的字段,這是 FlatBuffers 向前和向后兼容的機(jī)制。需要注意的是:

新的字段只能加在 table 的后面。舊的代碼會忽略這個字段,仍然可以正常執(zhí)行。新的代碼讀取舊的數(shù)據(jù),新增的字段會返回默認(rèn)值。
即使字段不再使用了也不能從 schema 中刪除??梢詷?biāo)記為 deprecated,在生成代碼的時候該字段不會生成該字段的接口。

四 FlatBuffers 的反序列化

FlatBuffers 反序列化的過程就很簡單了。由于序列化的時候保存好了各個字段的 offset,反序列化的過程其實就是把數(shù)據(jù)從指定的 offset 中讀取出來。反序列化的過程是把二進(jìn)制流從 root table 往后讀。從 vtable 中讀取對應(yīng)的 offset,然后在對應(yīng)的 object 中找到對應(yīng)的字段,如果是引用類型,string / vector / table,讀取出 offset,再次尋找 offset 對應(yīng)的值,讀取出來。如果是非引用類型,根據(jù) vtable 中的 offset ,找到對應(yīng)的位置直接讀取即可。對于標(biāo)量,分 2 種情況,默認(rèn)值和非默認(rèn)值。默認(rèn)值的字段,在讀取的時候,會直接從 flatc 編譯后的文件中記錄的默認(rèn)值中讀取出來。非默認(rèn)值字段,二進(jìn)制流中就會記錄該字段的 offset,值也會存儲在二進(jìn)制流中,反序列化時直接根據(jù)offset讀取字段值即可。

整個反序列化的過程零拷貝,不消耗占用任何內(nèi)存資源。并且 FlatBuffers 可以讀取任意字段,而不是像 Json 和 protocol buffer 需要讀取整個對象以后才能獲取某個字段。FlatBuffers 的主要優(yōu)勢就在反序列化這里了。所以 FlatBuffers 可以做到解碼速度極快,或者說無需解碼直接讀取。

五 FlatBuffers 的自動化

FlatBuffers 的自動化包括自動生成編碼解碼接口和自動生成 Json,自動化生成編解碼接口和自動生成 Json,都依賴 schem 的解析。

1 schema 描述文件解析

FlatBuffers 描述文件解析器按游標(biāo)的方式順序進(jìn)行識別 FlatBuffers 支持的數(shù)據(jù)結(jié)構(gòu)。獲取字段名稱、字段類型、字段默認(rèn)值、是否棄用等屬性。支持關(guān)鍵字:標(biāo)量類型、非標(biāo)量類型、include、namespace、root_type。

如果需要嵌套的vector,可以將vector包裝在table中。

2 自動生成編碼解碼接口

FlatBuffers 使用模板編程,編碼解碼接口僅生成h文件。實現(xiàn)數(shù)據(jù)結(jié)構(gòu)的定義,并特化出變量的Add函數(shù)、Get函數(shù),校驗函數(shù)接口。對應(yīng)的文件名為filename_generated.h。

3 自動生成Json

FlatBuffers 的主要目標(biāo)是避免反序列化。通過定義二進(jìn)制數(shù)據(jù)協(xié)議來實現(xiàn)的,一種將定義好的將數(shù)據(jù)轉(zhuǎn)換為二進(jìn)制數(shù)據(jù)的方法。由該協(xié)議創(chuàng)建的二進(jìn)制結(jié)構(gòu)無需進(jìn)一步解碼即可讀取。因此在自動生成json時,只需要提供二進(jìn)制數(shù)據(jù)流和二進(jìn)制定義結(jié)構(gòu)就可以讀物數(shù)據(jù),轉(zhuǎn)換成json。

Json結(jié)構(gòu)與 FlatBuffers 結(jié)構(gòu)保持一致。
默認(rèn)值不輸出 Json。

六 FlatBuffers 的優(yōu)缺點(diǎn)

FlatBuffers 通過 Scheme 文件定義數(shù)據(jù)結(jié)構(gòu),Schema 定義與其他框架使用的IDL(Interface description language)語言類似簡單易懂,F(xiàn)latBuffers 的 Scheme 是一種類C的語言(盡管 FlatBuffers 有自己的接口定義語言Scheme來定義要與之序列化的數(shù)據(jù),但它也支持 Protocol Buffers 中的 .proto 格式)。下面以官方 Tutorial 中的 monster.fbs 為例進(jìn)行說明:

1 優(yōu)點(diǎn)

解碼速度極快,將序列化數(shù)據(jù)存儲在緩存中,這些數(shù)據(jù)既可以寫出至文件中,又可以通過網(wǎng)絡(luò)原樣傳輸,也可直接讀取而沒有任何解析開銷,訪問數(shù)據(jù)時的唯一內(nèi)存需求就是緩沖區(qū),不需要額外的內(nèi)存分配。
擴(kuò)展性、靈活性:它支持的可選字段意味著具有很好的前向/后向兼容。FlatBuffers 支持選擇性地寫入數(shù)據(jù)成員,這不僅為某一個數(shù)據(jù)結(jié)構(gòu)在應(yīng)用的不同版本之間提供了兼容性,同時還能使程序員靈活地選擇是否寫入某些字段及靈活地設(shè)計傳輸?shù)臄?shù)據(jù)結(jié)構(gòu)。
跨平臺:支持 C++11、Java,而不需要任何依賴庫,在最新的 gcc、clang、vs2010 等編輯器上也工作良好。使用簡單方便 ,僅僅需要自動生成的少量代碼和一個單一的頭文件依賴,很容易集成到現(xiàn)有系統(tǒng)中,生成的 C++ 代碼提供了簡單的訪問和構(gòu)造接口,可以兼容 Json 等其他格式的解析。

2 缺點(diǎn)

數(shù)據(jù)無可讀性,必須進(jìn)行數(shù)據(jù)可視化才能理解數(shù)據(jù)。
向后兼容性局限,在 schema 中添加或刪除字段必須小心。

七 總結(jié)

相比其它的序列化工具,F(xiàn)latBuffers 最大的優(yōu)勢是反序列化速度極快,或者說無需解碼。如果使用場景是需要經(jīng)常解碼序列化的數(shù)據(jù),則有可能從 FlatBuffers 的特性獲得一定的好處。

責(zé)任編輯:梁菲 來源: 阿里云云棲號
相關(guān)推薦

2018-12-25 08:00:00

2022-02-25 08:54:50

setState異步React

2021-03-16 08:54:35

AQSAbstractQueJava

2011-07-04 10:39:57

Web

2020-11-06 09:24:09

node

2021-08-10 14:10:02

Nodejs后端開發(fā)

2017-07-02 18:04:53

塊加密算法AES算法

2019-01-07 15:29:07

HadoopYarn架構(gòu)調(diào)度器

2012-05-21 10:06:26

FrameworkCocoa

2022-09-26 09:01:15

語言數(shù)據(jù)JavaScript

2017-08-24 15:09:13

GAN神經(jīng)網(wǎng)絡(luò)無監(jiān)督學(xué)習(xí)

2020-12-09 09:59:40

Redis原理實戰(zhàn)

2023-01-06 12:50:46

ChatGPT

2019-11-21 09:16:14

OpenStack安全組MAC

2022-05-06 07:19:11

DOMDiff算法

2010-07-26 12:57:12

OPhone游戲開發(fā)

2016-10-14 13:53:05

JavascriptDOMWeb

2016-10-14 14:32:58

JavascriptDOMWeb

2009-11-17 17:31:58

Oracle COMM

2021-07-19 11:54:15

MySQL優(yōu)先隊列
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號