前不久,三方組件庫上新了一批JS/eTS組件,其中就包括okio組件。okio是一個(gè)可應(yīng)用于HarmonyOS的高效IO庫,它依托于系統(tǒng)能力,提供字符串的編解碼轉(zhuǎn)換能力,基礎(chǔ)數(shù)據(jù)類型的讀寫能力以及對(duì)文件讀寫的支持。本期將為大家介紹okio的工作原理及使用方法。
一、okio的產(chǎn)生背景
IO,即輸入輸出(Input/Output)。絕大多數(shù)應(yīng)用都需要與外部進(jìn)行數(shù)據(jù)交互,這就會(huì)涉及IO。系統(tǒng)提供了IO能力,在使用系統(tǒng)IO時(shí),通常需要一個(gè)中間緩沖區(qū)來保存讀取到的數(shù)據(jù)。數(shù)據(jù)先從輸入流緩沖區(qū)復(fù)制到中間緩沖區(qū),再從中間緩沖區(qū)復(fù)制到輸出流緩沖區(qū)。中間多次拷貝,降低了IO效率,同時(shí)增加了系統(tǒng)消耗。
為了滿足開發(fā)者對(duì)IO的更高要求,三方組件庫推出IO處理利器——okio(JS版本)。
okio使用Segment作為數(shù)據(jù)存儲(chǔ)容器,通過提供Segment移動(dòng)、共享、合并和分割的能力,讓數(shù)據(jù)讀寫變得非常靈活,也減少了數(shù)據(jù)復(fù)制,提升了IO效率。此外,okio還通過SegmentPool對(duì)Segment進(jìn)行回收和復(fù)用,減少大量創(chuàng)建Segment帶來的系統(tǒng)消耗。
下面就帶大家深入了解JS版本的okio的工作原理,探索它是如何提升IO效率的~
二、兩個(gè)基本概念
在深入解析okio的工作原理之前,我們先來了解兩個(gè)基本概念:Segment和SegmentPool。
1. Segment
okio將數(shù)據(jù)分割成一塊塊的片段存放在Segment里面。Segment是一個(gè)數(shù)據(jù)存儲(chǔ)的真正類,內(nèi)部維護(hù)著一個(gè)大小為8192字節(jié)的字節(jié)數(shù)組用于存儲(chǔ)數(shù)據(jù)。Segment最小可共享、可寫入的數(shù)據(jù)大小為1024字節(jié)。Segment使用pos、limit、shared、owner、prev、next來分別記錄讀寫位置、是否可寫入、是否能共享、數(shù)據(jù)擁有者、前置節(jié)點(diǎn)和后置節(jié)點(diǎn)信息。Segment對(duì)外提供sharedCopy、unsharedCopy、split、push、pop、compact、writeTo等接口用于操作數(shù)據(jù)。
Segment同時(shí)擁有前置節(jié)點(diǎn)和后置節(jié)點(diǎn),構(gòu)成一個(gè)雙向鏈表。讀取數(shù)據(jù)的時(shí)候,從雙向鏈表的頭部開始讀取;而寫入數(shù)據(jù)的時(shí)候,從雙向鏈表的尾部寫入數(shù)據(jù)。
2. SegmentPool
為了管理Segment,okio維護(hù)了一個(gè)Segment對(duì)象池(即SegmentPool),對(duì)廢棄的Segment回收、復(fù)用和內(nèi)存共享,從而減少內(nèi)存的申請(qǐng)和GC(garbage collection,垃圾收集)的頻率,使性能得到優(yōu)化。SegmentPool是一個(gè)由最多8個(gè)Segment組成的單鏈表。一個(gè)Segment的最大大小是8192字節(jié)(即8KB),所以SegmentPool的最大大小是64KB。
三、okio的工作原理
okio組件最重要的功能就是“讀”和“寫”。下面我們就從讀寫開始,了解okio的工作原理。
1. 讀寫數(shù)據(jù)
okio讀寫數(shù)據(jù)的過程中,遵循
大塊數(shù)據(jù)移動(dòng)、小塊數(shù)據(jù)復(fù)制
的原則。okio從輸入流讀取數(shù)據(jù)到輸入流緩沖區(qū)時(shí),會(huì)先找到雙向鏈表尾部的Segment節(jié)點(diǎn),如果此節(jié)點(diǎn)的剩余容量足夠,則直接將讀取到的數(shù)據(jù)存入到此節(jié)點(diǎn)。如果此節(jié)點(diǎn)的剩余容量不足,則從SegmentPool里面取一個(gè)Segment鏈接到雙向鏈表的尾部,然后將數(shù)據(jù)存入這個(gè)新節(jié)點(diǎn)。okio從輸入流緩沖區(qū)讀取數(shù)據(jù),再寫入數(shù)據(jù)到輸出流緩沖區(qū)。這個(gè)過程比較復(fù)雜,有以下幾種情況:
(1) 從輸入流緩沖區(qū)獲取到Segment,如果數(shù)據(jù)是滿的(字節(jié)數(shù)組data長度為8092字節(jié)),那么直接修改此Segment的prev和next信息,將其添加到輸出流緩沖區(qū)的雙向鏈表的尾部,省去一次數(shù)據(jù)復(fù)制過程。
圖1大塊數(shù)據(jù)移動(dòng)
(2) 從輸入流緩沖區(qū)獲取到Segment(假設(shè)為Segment1),如果數(shù)據(jù)不是滿的,可以通過pos和limit信息來確定segment1的可讀數(shù)據(jù),再和輸出流緩沖區(qū)的雙向鏈表的尾部節(jié)點(diǎn)(假設(shè)為Segment2)的剩余容量進(jìn)行對(duì)比:
如果Segment1的可讀數(shù)據(jù)比Segment2的剩余容量小,則把Segment1的數(shù)據(jù)復(fù)制到Segment2,然后回收Segment1到SegmentPool。如果Segment1的可讀數(shù)據(jù)比Segment2的剩余容量大,那么直接修改Segment1的prev和next信息,將其添加到Segment2的后面。
(3) 從輸入流緩沖區(qū)獲取到Segment(假設(shè)為Segment3),如果只需要傳遞部分?jǐn)?shù)據(jù)(比如總數(shù)據(jù)為4096字節(jié),只傳遞1024字節(jié)),okio會(huì)通過split接口將Segment3拆分成含3072字節(jié)數(shù)據(jù)的Segment3-1和含1024字節(jié)數(shù)據(jù)的Segment3-2,然后按照(2)的邏輯將Segment3-2的數(shù)據(jù)寫入輸出流緩沖區(qū)。
圖2 Segment拆分
拆分Segment的時(shí)候,可以通過參數(shù)指定拆分后的第一個(gè)Segment含有的未讀字節(jié)數(shù)(byteCount)。拆分后,第一個(gè)Segment包含的數(shù)據(jù)范圍是[pos,pos+byteCount),第二個(gè)Segment包含的數(shù)據(jù)范圍是[pos+byteCount,limit)。拆分Segment時(shí)也遵循大塊數(shù)據(jù)移動(dòng)、小塊數(shù)據(jù)復(fù)制的原則。當(dāng)byteCount大于1024時(shí),使用共享的Segment,否則復(fù)制數(shù)據(jù)。
(注:文件、流、socket相關(guān)的IO優(yōu)化需要系統(tǒng)支持,待后續(xù)版本優(yōu)化提供。)
2. Segment的回收與復(fù)用
接下來,我們?cè)賮砜纯碨egmentPool是如何回收和復(fù)用Segment的。
每次okio想要使用Segment就從SegmentPool中獲取,使用完畢后又會(huì)放回到SegmentPool中等待復(fù)用,核心方法為take()和recycle()。
(1) take()方法
take()方法負(fù)責(zé)從對(duì)象池單鏈表的頭部獲取可以使用的Segment。如果獲取不到,說明單鏈表是空的,此時(shí)新創(chuàng)建一個(gè)Segment給緩沖區(qū)使用。如果能獲取到,則取出單鏈表的頭部節(jié)點(diǎn),再將下一個(gè)節(jié)點(diǎn)置為單鏈表的頭部節(jié)點(diǎn),并將取出來的Segment的next置空,同時(shí)更新對(duì)象池大小。
(2) recycle()方法
recycle()方法負(fù)責(zé)回收緩沖區(qū)里面使用完畢的Segment。回收開始時(shí),首先更新對(duì)象池大小,然后把回收對(duì)象Segment添加到單鏈表頭部,接著重置Segment的pos和limit為0。注意,以下情況不會(huì)回收Segment:
當(dāng)前Segment的prev和next不為空
當(dāng)前Segment是共享的
對(duì)象池已經(jīng)有8個(gè)Segment了
3. 字符串處理
除了Segment和SegmentPool外,okio還封裝了ByteString類來進(jìn)行字符串處理。ByteString提供Base64編解碼、utf-8編碼、十六進(jìn)制編解碼、大小寫轉(zhuǎn)換、內(nèi)容比較等豐富的API,可以很方便地處理字符串。在進(jìn)行字符串處理時(shí),由于ByteString同時(shí)持有原始字符串和對(duì)應(yīng)的字節(jié)數(shù)組,可以直接使用字節(jié)數(shù)組里面的數(shù)據(jù)進(jìn)行操作,不需要先將字符串轉(zhuǎn)換為字節(jié)數(shù)組。特別是在頻繁轉(zhuǎn)換編碼的場景下,通過這種以空間換時(shí)間的方式,可以避免字符串與字節(jié)數(shù)組的多次轉(zhuǎn)換,減少了時(shí)間和系統(tǒng)性能消耗。
四、okio的使用及示例
1. 前置配置
步驟一:在entry 的package.json文件中添加以下依賴項(xiàng)。
"dependencies": {
步驟二:配置倉庫鏡像地址。
npm config set @ohos:registry=https://repo.harmonyos.com/npm/
步驟三:DevEco Studio的Terminal里面輸入以下命令下載源代碼。
cd entry
步驟四:文件的頭部引入okio庫。
import okio from '@ohos/okio';
步驟五:在config.json文件中申請(qǐng)存儲(chǔ)權(quán)限。
2. 代碼實(shí)現(xiàn)
執(zhí)行完上面的配置操作后,就可以進(jìn)入代碼編寫階段了。開發(fā)者可以使用okio提供的豐富的API接口來實(shí)現(xiàn)功能。下面為大家展示四個(gè)實(shí)現(xiàn)示例,供大家參考學(xué)習(xí)。
示例1:文件寫入和讀取
本示例通過sink將內(nèi)容寫入文件,通過source從文件讀取內(nèi)容。代碼如下:
//通過sink將內(nèi)容寫入文件
示例2:Base64解碼
本示例通過ByteString實(shí)現(xiàn)Base64解碼功能,代碼如下:
let byteStringObj = new okio.ByteString.ByteString(''); //生成ByteString對(duì)象
示例3:十六進(jìn)制解碼
本示例通過ByteString實(shí)現(xiàn)十六進(jìn)制解碼功能,代碼如下:
let byteStringObj = new okio.ByteString.ByteString('');
示例4:Utf8編碼
本示例通過ByteString實(shí)現(xiàn)Utf8編碼功能,代碼如下:
let byteStringObj = new okio.ByteString.ByteString('');
本期okio組件就為大家介紹到這里了。okio組件已開源,歡迎大家參與貢獻(xiàn)。
-
開源
+關(guān)注
關(guān)注
3文章
3254瀏覽量
42408 -
存儲(chǔ)數(shù)據(jù)
+關(guān)注
關(guān)注
0文章
85瀏覽量
14092 -
HarmonyOS
+關(guān)注
關(guān)注
79文章
1967瀏覽量
30025
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論