Android進(jìn)階之從IO到NIO的模型機(jī)制演進(jìn)
引言
其實(shí)IO操作相較于服務(wù)端,客戶端做的并不多,基本的場(chǎng)景就是讀寫文件的時(shí)候會(huì)使用到InputStream或者OutputStream,然而客戶端能做的就是發(fā)起一個(gè)讀寫的指令,真正的操作是內(nèi)核層通過(guò)ioctl指令執(zhí)行讀寫操作,因?yàn)槊看蔚腎O操作都涉及到了線程的操作,因此會(huì)有性能上的損耗,那么從本篇文章開(kāi)始,我們將進(jìn)入IO的世界,了解IO到NIO機(jī)制的演進(jìn),從底層關(guān)注序列化的原理。
1 Basic IO模型
那么在Java(Kotlin)中,IO主要分為兩種:Basic IO 和 Net IO;Basic IO是我們?cè)陂_(kāi)發(fā)當(dāng)中常用的一些IO流,例如:
FileInputStream://文件輸入流 FileOutputStream://文件輸出流 BufferedInputStream://緩存字節(jié)輸入流 BufferedOutputStream://緩存字節(jié)輸入流,此類數(shù)據(jù)流為了提高讀寫效率,可以緩存數(shù)據(jù)到buffer,通過(guò)flush一起寫入;內(nèi)核分配內(nèi)存為一頁(yè)4K,但是Java緩沖區(qū)默認(rèn)是8K ObjectInputStream ObjectOutputStream:// 將數(shù)據(jù)序列化處理 RandomAccessFile://提供位移數(shù)據(jù)插入
對(duì)于前面的幾個(gè)數(shù)據(jù)流,我就不介紹用法了,對(duì)于最后一個(gè)RandomAccessFile,我想簡(jiǎn)單介紹一下,因?yàn)楹芏嗷锇閭兛赡懿恢繰andomAccessFile的存在,這里曾經(jīng)有個(gè)面試題:
假設(shè)有一個(gè)5G的文件,我想在文章的末尾追加一段話,我該怎么處理?或者我指定任意位置添加一部分文字內(nèi)容,該怎么處理?
很多伙伴看到這個(gè)問(wèn)題之后,一拍腦門說(shuō):先通過(guò)FileInputStream把文件讀寫進(jìn)來(lái),然后再在末尾追加一部分內(nèi)容組合成新的字節(jié)流,然后再通過(guò)FileOutputStream寫入到新的文件中。
完蛋,直接pass掉!因?yàn)?strong>前提這里已經(jīng)是5G的文件了,如果通過(guò)FileInputStream讀寫,大概率就會(huì)直接OOM! 所以如果知道RandomAccessFile的存在,這些就不是問(wèn)題了。
fun testAccessFile() {
//file文件
val file = File("/storage/emulated/0/NewTextFile.txt")
val accessFile = RandomAccessFile(file, "rw")
//先寫一段
val text = "IO主要分為兩種:Basic IO 和 Net IO;"
accessFile.write(text.toByteArray())
//再等5s
Thread.sleep(5000)
accessFile.seek(5)
accessFile.write("seek to pos 5".toByteArray())
accessFile.close()
}
首先我們常見(jiàn)一個(gè)RandomAccessFile,傳入要讀寫的文件,首先寫入一段話,然后等到5s后,調(diào)用RandomAccessFile的seek方法,此時(shí)指針就是移動(dòng)到了文件第五個(gè)字符的位置,然后又寫入了一些文字。

所以按照這種思想,回到前面的問(wèn)題,即便是5G的文件,也不需要進(jìn)行讀寫操作獲取之前的全部數(shù)據(jù)就能夠?qū)崿F(xiàn)零內(nèi)存追加;當(dāng)然還有一個(gè)場(chǎng)景也會(huì)經(jīng)常用到,就是斷點(diǎn)續(xù)傳。
1.1 RandomAccessFile的緩沖區(qū)和BufferedInputStream緩沖區(qū)的區(qū)別
首先我先簡(jiǎn)單介紹下BufferedInputStream的緩存區(qū)效果,系統(tǒng)內(nèi)核緩存區(qū)默認(rèn)為4K,當(dāng)緩存區(qū)滿4K之后會(huì)進(jìn)行磁盤的寫入;那么在Java中是對(duì)其做了優(yōu)化處理,將緩存區(qū)變?yōu)?K,當(dāng)緩存區(qū)超過(guò)8K之后,會(huì)將數(shù)據(jù)復(fù)制給到內(nèi)核緩存。

fun testBuffer() {
val file = File("/storage/emulated/0/NewTextFile.txt")
val bis = BufferedOutputStream(FileOutputStream(file))
val text = "8888888888888888".toByteArray()
bis.write(text, 0, text.size)
// bis.flush()
}
例如上面的案例,此時(shí)App的內(nèi)存緩存區(qū)沒(méi)有滿,那么如果不調(diào)用flush,那么數(shù)據(jù)不會(huì)寫到磁盤文件中,只有當(dāng)緩沖區(qū)滿了之后,才會(huì)復(fù)制到內(nèi)核空間緩存區(qū)。
fun testAccessFile() {
//file文件
val file = File("/storage/emulated/0/NewTextFile.txt")
val accessFile = RandomAccessFile(file, "rw")
//先寫一段
val text = "IO主要分為兩種:Basic IO 和 Net IO;"
accessFile.write(text.toByteArray())
//再等5s
Thread.sleep(5000)
accessFile.seek(5)
val channel = accessFile.channel
val mapper = channel.map(FileChannel.MapMode.READ_WRITE, channel.position(), channel.size())
mapper.put("seek to pos 5".toByteArray())
}
如果按照BufferedOutputStream的思想,我們往緩沖區(qū)寫數(shù)據(jù),沒(méi)有flush就不會(huì)有復(fù)制的操作,那么我們實(shí)際看到的是數(shù)據(jù)還是寫進(jìn)去了。

其實(shí)MappedByteBuffer,是提供了一個(gè)類似于mmap性質(zhì)的能力,實(shí)現(xiàn)了App緩沖區(qū)與內(nèi)核緩沖區(qū)的橋接或者映射。

當(dāng)App寫入緩存數(shù)據(jù)的時(shí)候,直接映射到了內(nèi)核緩存區(qū),完成了磁盤的讀寫操作。
1.2 Basic IO模型底層原理
其實(shí)對(duì)于基礎(chǔ)的IO模型,也就是Basic IO的實(shí)現(xiàn)是阻塞的,其實(shí)我們也可以自己驗(yàn)證,在主線程中進(jìn)行讀寫操作就是阻塞的。
那么對(duì)于IO來(lái)說(shuō),主要分為兩個(gè)階段:
(1)數(shù)據(jù)準(zhǔn)備階段;這里是由Java實(shí)現(xiàn)的,寫入到JVM中;
(2)復(fù)制階段;內(nèi)核空間復(fù)制用戶空間緩存數(shù)據(jù),這部分需要調(diào)用內(nèi)核函數(shù)(ioctl、sync),完成復(fù)制的工作。
剩下的磁盤寫入操作就完全是由內(nèi)核完成的,如果對(duì)于讀寫操作有疑問(wèn)的,可以去看看下面這篇對(duì)于Binder底層原理的介紹。
Android Framework原理 -- Binder驅(qū)動(dòng)源碼分析
對(duì)于傳統(tǒng)的Socket來(lái)說(shuō),這種屬于Net IO,本質(zhì)也是阻塞性質(zhì)的,例如App進(jìn)程想要獲取一些數(shù)據(jù),

上圖展示了read操作的整個(gè)調(diào)度過(guò)程:
(1)當(dāng)App調(diào)用系統(tǒng)方法想要獲取某些數(shù)據(jù)的時(shí)候,首先系統(tǒng)內(nèi)核會(huì)等待數(shù)據(jù)從網(wǎng)絡(luò)中到達(dá),這個(gè)過(guò)程內(nèi)核處于阻塞的狀態(tài);
(2)等到數(shù)據(jù)到達(dá)之后,就會(huì)將網(wǎng)絡(luò)數(shù)據(jù)復(fù)制到用戶空間的緩沖區(qū)中,并通知App進(jìn)程復(fù)制數(shù)據(jù)成功,此時(shí)App中其他業(yè)務(wù)才能夠繼續(xù)執(zhí)行。
所以整個(gè)過(guò)程中,App處于阻塞狀態(tài),而在高并發(fā)的場(chǎng)景中(客戶端很少,這里拿服務(wù)端來(lái)舉例),例如10000QPS(每秒10000次查詢操作),此時(shí)如果采用IO阻塞模型,帶來(lái)的后果就是CPU極速拉滿最終可能導(dǎo)致熔斷,所以針對(duì)這種情況,出現(xiàn)了NIO模型。
2 NIO模型
相對(duì)于IO模型來(lái)說(shuō),NIO模型做的優(yōu)化是通過(guò)輪詢機(jī)制獲取內(nèi)核的數(shù)據(jù)等待狀態(tài),看下圖:

當(dāng)一次詢問(wèn)發(fā)出之后,如果當(dāng)前內(nèi)核還是數(shù)據(jù)等待狀態(tài),那么內(nèi)核空間會(huì)被”掛起“,此時(shí)App進(jìn)程可以做其他的事情,等到下一次輪詢時(shí)間到了之后,再次發(fā)起詢問(wèn),如果此時(shí)已經(jīng)拿到了數(shù)據(jù),那么就會(huì)進(jìn)行復(fù)制操作,將數(shù)據(jù)放入用戶進(jìn)程緩沖區(qū)。

那么對(duì)此,java.nio包下提供了很多非阻塞IO的API,例如我們前面提到的MappedByteBuffer。其實(shí)還是前面我們探討的一個(gè)問(wèn)題,在Android的場(chǎng)景下,很難碰到高并發(fā)的場(chǎng)景,所以基本上也很難用到這個(gè),但是對(duì)于NIO模型的原理我們需要掌握透徹,在面試中可能會(huì)涉及到這些問(wèn)題。
3 OKIO
最后介紹一個(gè)IO模型---OKIO,如果使用到OkHttp的伙伴們應(yīng)該已經(jīng)見(jiàn)到過(guò)這個(gè),但是沒(méi)有實(shí)際地去研究,為啥要引入這個(gè)okio三方庫(kù)。
首先okio是OkHttp團(tuán)隊(duì)基于Basic IO研發(fā)的一套自己的IO體系,為啥要搞一個(gè)這個(gè)玩意出來(lái)呢?通過(guò)前面我們分析Basic IO存在的一些問(wèn)題,首先 Basic IO是阻塞的,而且在客戶端端如果頻繁地進(jìn)行網(wǎng)絡(luò)請(qǐng)求,而且網(wǎng)絡(luò)請(qǐng)求是雙向的,從客戶端發(fā)出請(qǐng)求,服務(wù)端返回響應(yīng),那么這個(gè)過(guò)程必定會(huì)使用到InputStream和OutputStream。
因?yàn)镺kHttp是有自己的緩存策略的,如果使用到緩存,那么對(duì)于InputStream就需要一個(gè)buffer,對(duì)于OutputStream也需要一個(gè)buffer,每次讀寫操作都需要兩個(gè)buffer來(lái)做支撐,因此針對(duì)這種場(chǎng)景,okio在底層做了處理。
具體的處理就是不再使用byte[]數(shù)組存儲(chǔ)數(shù)據(jù),而是采用Segment數(shù)據(jù)結(jié)構(gòu)。有熟悉Segment的伙伴應(yīng)該知道,它是一個(gè)數(shù)組的雙向鏈表,其中data就是一個(gè)byte數(shù)組,其中有next和pre兩個(gè)指針。
internal class Segment {
@JvmField val data: ByteArray
/** The next byte of application data byte to read in this segment. */
@JvmField var pos: Int = 0
/** The first byte of available data ready to be written to. */
@JvmField var limit: Int = 0
/** True if other segments or byte strings use the same byte array. */
@JvmField var shared: Boolean = false
/** True if this segment owns the byte array and can append to it, extending `limit`. */
@JvmField var owner: Boolean = false
/** Next segment in a linked or circularly-linked list. */
@JvmField var next: Segment? = null
/** Previous segment in a circularly-linked list. */
@JvmField var prev: Segment? = null

當(dāng)進(jìn)行讀寫操作的時(shí)候,都會(huì)往Segment中寫入,就是將InputStream和OutputStream需要?jiǎng)?chuàng)建的緩沖區(qū)合并。
這里需要說(shuō)明一點(diǎn),okio屬于OkHttp內(nèi)部核心IO框架,并不是單獨(dú)拿出來(lái)任意業(yè)務(wù)方可以使用,所以對(duì)于okio的具體實(shí)現(xiàn)原理,后續(xù)會(huì)放在OkHttp框架原理中做詳細(xì)的介紹。
以上就是Android進(jìn)階之從IO到NIO的模型機(jī)制演進(jìn)的詳細(xì)內(nèi)容,更多關(guān)于Android模型從IO到NIO機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Android隱私協(xié)議提示彈窗的實(shí)現(xiàn)流程詳解
- Android?Jetpack組件ViewModel基本用法詳解
- Android進(jìn)階從字節(jié)碼插樁技術(shù)了解美團(tuán)熱修復(fù)實(shí)例詳解
- Android進(jìn)階Handler應(yīng)用線上卡頓監(jiān)控詳解
- Android進(jìn)階KOOM線上APM監(jiān)控全面剖析
- Android進(jìn)階CoordinatorLayout協(xié)調(diào)者布局實(shí)現(xiàn)吸頂效果
- Android進(jìn)階NestedScroll嵌套滑動(dòng)機(jī)制實(shí)現(xiàn)吸頂效果詳解
相關(guān)文章
自定義視圖view使用Canvas實(shí)現(xiàn)手寫板和涂鴉功能
這篇文章主要介紹了自定義視圖view使用Canvas實(shí)現(xiàn)手寫板和涂鴉功能,這里直接上代碼,里面有詳細(xì)講解和注釋,需要的朋友可以參考下2023-04-04
Android App中的GridView網(wǎng)格布局使用指南
GridView布局所實(shí)現(xiàn)的就是類似于九宮格的矩陣界面效果,下面整理了Android App中的GridView網(wǎng)格布局使用指南,包括分割線的添加與自定義GridView的實(shí)現(xiàn)等技巧,需要的朋友可以參考下2016-06-06
Flutter學(xué)習(xí)之實(shí)現(xiàn)自定義themes詳解
一般情況下我們?cè)趂lutter中搭建的app基本上都是用的是MaterialApp這種設(shè)計(jì)模式,MaterialApp中為我們接下來(lái)使用的按鈕,菜單等提供了統(tǒng)一的樣式,那么這種樣式能不能進(jìn)行修改或者自定義呢?答案是肯定的,一起來(lái)看看吧2023-03-03
Android實(shí)現(xiàn)圖片滾動(dòng)和頁(yè)簽控件功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android實(shí)現(xiàn)圖片滾動(dòng)控件含頁(yè)簽功能的實(shí)現(xiàn)代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,一起跟隨小編過(guò)來(lái)看看吧2018-05-05
Android手勢(shì)識(shí)別器GestureDetector使用詳解
這篇文章主要為大家詳細(xì)介紹了Android手勢(shì)識(shí)別器GestureDetector的使用方法解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
Android LocationManager獲取經(jīng)度與緯度等地理信息
這篇文章主要介紹了Android LocationManager獲取經(jīng)度與緯度等地理信息的相關(guān)資料,希望通過(guò)本站大家能掌握這樣的知識(shí),需要的朋友可以參考下2017-09-09
設(shè)置Android系統(tǒng)永不鎖屏永不休眠的方法
在進(jìn)行Android系統(tǒng)開(kāi)發(fā)的時(shí)候,有些特定的情況需要設(shè)置系統(tǒng)永不鎖屏,永不休眠。本篇文章給大家介紹Android 永不鎖屏,開(kāi)機(jī)不鎖屏,刪除設(shè)置中休眠時(shí)間選項(xiàng),需要的朋友一起學(xué)習(xí)吧2016-03-03
Flutter?SystemChrome控制應(yīng)用程序的系統(tǒng)級(jí)別行為
這篇文章主要為大家介紹了Flutter?SystemChrome用來(lái)控制應(yīng)用程序的系統(tǒng)級(jí)別行為步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05

