10 | Android 高級(jí)進(jìn)階(源碼剖析篇) Square 高效易用的 IO 框架 okio(三)

作者簡(jiǎn)介:ASCE1885, 《Android 高級(jí)進(jìn)階》作者榜苫。
本文由于潛在的商業(yè)目的铝宵,未經(jīng)授權(quán)不開(kāi)放全文轉(zhuǎn)載許可谦纱,謝謝看成!
本文分析的源碼版本已經(jīng) fork 到我的 Github

1520301887857.jpg

本文我們重點(diǎn)來(lái)介紹 okio 中的 segment 和 SegmentPool跨嘉,segment 是一個(gè)底層的數(shù)據(jù)結(jié)構(gòu)川慌,本質(zhì)上是一個(gè)字節(jié)數(shù)組,同時(shí)由于它被用于鏈表或者循環(huán)鏈表中,因此梦重,segment 中也定義了鏈表的相關(guān)操作兑燥。而 SegmentPool 中保存的是未使用的 segment 的集合,需要使用 segment 時(shí)從這個(gè)池子中取用琴拧,用完后及時(shí)釋放回這個(gè)池子降瞳,兩者的關(guān)系如下圖所示:

關(guān)系圖

segment

接下來(lái)我們首先介紹 segment,從上圖可以看到蚓胸,segment 有兩個(gè)核心概念:

  • 讀取指針 pos:segment 數(shù)據(jù)讀取時(shí)下一個(gè)要讀取的字節(jié)位置
  • 寫入指針 limit:segment 數(shù)據(jù)寫入時(shí)將會(huì)寫入的第一個(gè)字節(jié)位置

segment 根據(jù)其中的字節(jié)數(shù)據(jù) final byte[] data 是否能夠被多個(gè) segment 共用挣饥,可以分為共享 segment非共享 segment,通過(guò)變量 boolean shared 來(lái)標(biāo)識(shí)沛膳;同時(shí)同一份數(shù)據(jù)多個(gè) segment 在使用時(shí)扔枫,通過(guò)變量 boolean owner 來(lái)區(qū)分誰(shuí)是這份數(shù)據(jù)的擁有者,一份數(shù)據(jù)只有一個(gè)擁有者于置,其他的都是使用者茧吊,一般來(lái)說(shuō)贞岭,數(shù)據(jù)擁有者對(duì)共享的 final byte[] data 數(shù)組擁有讀寫權(quán)限八毯,數(shù)據(jù)使用者對(duì)其只有只讀權(quán)限。

從 Segment 類的構(gòu)造方法也可以看出它是否共享 segment:

/** segment 中字節(jié)數(shù)組的大小 */
static final int SIZE = 8192;

/** segment 拆分時(shí)瞄桨,當(dāng)數(shù)據(jù)量少于這個(gè)值時(shí)不做數(shù)據(jù)共享 */
static final int SHARE_MINIMUM = 1024;

/** segment 中實(shí)際存放數(shù)據(jù)的地方 */
final byte[] data;

/** 數(shù)據(jù)讀取指針 */
int pos;

/** 數(shù)據(jù)寫入指針 */
int limit;

/** 為 true 表示有其他 segment 和當(dāng)前 segment 共享 data 數(shù)組 */
boolean shared;

/** 為 true 時(shí)表示當(dāng)前 segment 擁有 data 數(shù)組话速,并能夠進(jìn)行數(shù)據(jù)的寫入 */
boolean owner;
  
/** 非共享 segment */
Segment() {
    this.data = new byte[SIZE];
    this.owner = true;
    this.shared = false;
}

/** 共享 segment */
Segment(Segment shareFrom) {
    this(shareFrom.data, shareFrom.pos, shareFrom.limit);
    shareFrom.shared = true;
}

/** 共享 segment */
Segment(byte[] data, int pos, int limit) {
    this.data = data;
    this.pos = pos;
    this.limit = limit;
    this.owner = false;
    this.shared = true;
}

鏈表操作

當(dāng) segment 被用于鏈表或者循環(huán)鏈表時(shí),就會(huì)存在指向鏈表前面一個(gè) segment 的指針 prev芯侥,或者指向后面一個(gè) segment 的指針 next泊交,同時(shí)存在鏈表元素的添加和刪除,熟悉數(shù)據(jù)結(jié)構(gòu)的同學(xué)應(yīng)該知道柱查,這是典型的鏈表操作廓俭,鏈表元素的添加和刪除基本上就是指針的指向操作。

雙向鏈表在某個(gè)元素后面插入一個(gè)新元素的步驟如下:

  • 將新元素的前向指針 prev 指向當(dāng)前元素
  • 將新元素的后向指針 next 指向當(dāng)前元素的后面一個(gè)元素唉工,也就是當(dāng)前元素的 next 指針
  • 當(dāng)前元素的前向指針改為指向新元素
  • 當(dāng)前元素下一個(gè)元素的前向指針改為指向新元素

代碼如下所示:

/** 鏈表或者雙向循環(huán)鏈表中當(dāng)前元素的后面一個(gè)元素指針 */
Segment next;

/** 雙向循環(huán)鏈表中當(dāng)前元素的前一個(gè)元素指針 */
Segment prev;

/** 在當(dāng)前 segment 后面添加一個(gè)新的 segment */
public Segment push(Segment segment) {
    segment.prev = this;
    segment.next = next;
    next.prev = segment;
    next = segment;
    return segment;
}

雙向鏈表刪除當(dāng)前元素并返回當(dāng)前元素的后面一個(gè)元素的步驟如下:

  • 獲取當(dāng)前元素的后面一個(gè)元素(緩存起來(lái))
  • 當(dāng)前元素的前一個(gè)元素的后向指針改為指向當(dāng)前元素的后一個(gè)元素
  • 當(dāng)前元素的后一個(gè)元素的前向指針改為指向當(dāng)前元素的前一個(gè)元素
  • 當(dāng)前元素的前向指針和后向指針都設(shè)置為空研乒,從而從鏈表中刪除

代碼如下所示:

/** 雙向鏈表刪除當(dāng)前元素并返回當(dāng)前元素的后面一個(gè)元素 */
public @Nullable Segment pop() {
    Segment result = next != this ? next : null;
    prev.next = next;
    next.prev = prev;
    next = null;
    prev = null;
    return result;
}

相信工科相關(guān)專業(yè)的同學(xué)應(yīng)該都學(xué)習(xí)過(guò)嚴(yán)蔚敏的那本經(jīng)典的數(shù)據(jù)結(jié)構(gòu)教程,印象不深的可以回味一下淋硝。

segment 的拆分

為了高效的利用內(nèi)存和方便 Buffer 的操作雹熬,segment 支持拆分和合并,拆分指的是將一個(gè) segment 中從讀取指針 pos 開(kāi)始到寫入指針 limit 之間的數(shù)據(jù)拆分到兩個(gè) segment 中谣膳。使用者需要指定一個(gè)字節(jié)數(shù) byteCount 用來(lái)分割這部分?jǐn)?shù)據(jù)竿报,[pos ~ pos+byteCount) 之間的數(shù)據(jù)拆分到新的 segment 中,[pos+byteCount ~ limit) 之間的數(shù)據(jù)保留在原來(lái)的 segment 中继谚。在鏈表的視角看烈菌,新拆分出來(lái)的 segment 位于原來(lái) segment 的前面。

在拆分的過(guò)程中,涉及到新 segment 的生成芽世,為了獲得高性能侨嘀,有如下兩個(gè)理念截然相反的問(wèn)題需要關(guān)注和平衡:

  • 盡可能避免數(shù)據(jù)拷貝,這個(gè)可以通過(guò)前面介紹過(guò)的共享 segment 來(lái)實(shí)現(xiàn)
  • 當(dāng)拷貝數(shù)據(jù)量很小時(shí)捂襟,避免使用共享 segment咬腕,因?yàn)楣蚕聿糠謹(jǐn)?shù)據(jù)是只讀的,而且可能會(huì)導(dǎo)致鏈表中存在很多數(shù)據(jù)量很小的 segment葬荷,影響性能

因此涨共,在拆分時(shí),只有當(dāng)數(shù)據(jù)量 byteCount 不小于 segment 大小的八分之一(即 1024 字節(jié))時(shí)宠漩,才會(huì)使用共享 segment举反,否則使用非共享 segment,并通過(guò) System.arraycopy 進(jìn)行數(shù)據(jù)的拷貝扒吁。然后修改新產(chǎn)生的 segment 的指針指向并將其插入鏈表中火鼻,代碼如下所示:

public Segment split(int byteCount) {
    // 參數(shù)范圍校驗(yàn)
    if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
    
    // 拆分出來(lái)的新 segment
    Segment prefix; 
    if (byteCount >= SHARE_MINIMUM) {
      // 共享 segment
      prefix = new Segment(this);
    } else {
      // 非共享 segment
      prefix = SegmentPool.take();
      System.arraycopy(data, pos, prefix.data, 0, byteCount);
    }

    prefix.limit = prefix.pos + byteCount;
    pos += byteCount;
    prev.push(prefix);
    return prefix;
}

上面代碼中如果拆分出來(lái)的新 segment 走的是共享 segment 分支代碼,那么拆分后新的 segment 和原來(lái)的 segment 在數(shù)據(jù)結(jié)構(gòu)上是共享的雕崩,也就是兩者的數(shù)據(jù)都存放在同一個(gè) final byte[] data 中魁索,兩者不同的只是讀取指針 pos 和寫入指針 limit 的位置,如下圖所示:

還有 56% 的精彩內(nèi)容
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
支付 ¥5.20 繼續(xù)閱讀
  • 序言:七十年代末盼铁,一起剝皮案震驚了整個(gè)濱河市粗蔚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饶火,老刑警劉巖鹏控,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異肤寝,居然都是意外死亡当辐,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門鲤看,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)缘揪,“玉大人,你說(shuō)我怎么就攤上這事刨摩∷律危” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵澡刹,是天一觀的道長(zhǎng)呻征。 經(jīng)常有香客問(wèn)我,道長(zhǎng)罢浇,這世上最難降的妖魔是什么陆赋? 我笑而不...
    開(kāi)封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任沐祷,我火速辦了婚禮,結(jié)果婚禮上攒岛,老公的妹妹穿的比我還像新娘赖临。我一直安慰自己,他們只是感情好灾锯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布兢榨。 她就那樣靜靜地躺著,像睡著了一般顺饮。 火紅的嫁衣襯著肌膚如雪吵聪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天兼雄,我揣著相機(jī)與錄音吟逝,去河邊找鬼。 笑死赦肋,一個(gè)胖子當(dāng)著我的面吹牛块攒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播佃乘,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼囱井,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了恕稠?” 一聲冷哼從身側(cè)響起琅绅,我...
    開(kāi)封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹅巍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體料祠,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骆捧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了髓绽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敛苇。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖顺呕,靈堂內(nèi)的尸體忽然破棺而出枫攀,到底是詐尸還是另有隱情,我是刑警寧澤株茶,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布来涨,位于F島的核電站,受9級(jí)特大地震影響启盛,放射性物質(zhì)發(fā)生泄漏蹦掐。R本人自食惡果不足惜技羔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卧抗。 院中可真熱鬧藤滥,春花似錦、人聲如沸社裆。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)泳秀。三九已至时呀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間晶默,已是汗流浹背谨娜。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留磺陡,地道東北人趴梢。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像币他,于是被迫代替她去往敵國(guó)和親坞靶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容