中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android之從IO到NIO的模型機制實例分析

發布時間:2023-02-01 09:42:26 來源:億速云 閱讀:76 作者:iii 欄目:開發技術

這篇文章主要講解了“Android之從IO到NIO的模型機制實例分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Android之從IO到NIO的模型機制實例分析”吧!

    1 Basic IO模型

    那么在Java(Kotlin)中,IO主要分為兩種:Basic IO 和 Net IO;Basic IO是我們在開發當中常用的一些IO流,例如:

    FileInputStream://文件輸入流
    FileOutputStream://文件輸出流
    BufferedInputStream://緩存字節輸入流
    BufferedOutputStream://緩存字節輸入流,此類數據流為了提高讀寫效率,可以緩存數據到buffer,通過flush一起寫入;內核分配內存為一頁4K,但是Java緩沖區默認是8K
    ObjectInputStream
    ObjectOutputStream:// 將數據序列化處理
    RandomAccessFile://提供位移數據插入

    對于前面的幾個數據流,我就不介紹用法了,對于最后一個RandomAccessFile,我想簡單介紹一下,因為很多伙伴們可能不知道RandomAccessFile的存在,這里曾經有個面試題:

    假設有一個5G的文件,我想在文章的末尾追加一段話,我該怎么處理?或者我指定任意位置添加一部分文字內容,該怎么處理?

    很多伙伴看到這個問題之后,一拍腦門說:先通過FileInputStream把文件讀寫進來,然后再在末尾追加一部分內容組合成新的字節流,然后再通過FileOutputStream寫入到新的文件中。

    完蛋,直接pass掉!因為前提這里已經是5G的文件了,如果通過FileInputStream讀寫,大概率就會直接OOM! 所以如果知道RandomAccessFile的存在,這些就不是問題了。

    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()
    }

    首先我們常見一個RandomAccessFile,傳入要讀寫的文件,首先寫入一段話,然后等到5s后,調用RandomAccessFile的seek方法,此時指針就是移動到了文件第五個字符的位置,然后又寫入了一些文字。

    Android之從IO到NIO的模型機制實例分析

    所以按照這種思想,回到前面的問題,即便是5G的文件,也不需要進行讀寫操作獲取之前的全部數據就能夠實現零內存追加;當然還有一個場景也會經常用到,就是斷點續傳。

    1.1 RandomAccessFile的緩沖區和BufferedInputStream緩沖區的區別

    首先我先簡單介紹下BufferedInputStream的緩存區效果,系統內核緩存區默認為4K,當緩存區滿4K之后會進行磁盤的寫入;那么在Java中是對其做了優化處理,將緩存區變為8K,當緩存區超過8K之后,會將數據復制給到內核緩存。

    Android之從IO到NIO的模型機制實例分析

    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()
        }

    例如上面的案例,此時App的內存緩存區沒有滿,那么如果不調用flush,那么數據不會寫到磁盤文件中,只有當緩沖區滿了之后,才會復制到內核空間緩存區。

    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的思想,我們往緩沖區寫數據,沒有flush就不會有復制的操作,那么我們實際看到的是數據還是寫進去了。

    Android之從IO到NIO的模型機制實例分析

    其實MappedByteBuffer,是提供了一個類似于mmap性質的能力,實現了App緩沖區與內核緩沖區的橋接或者映射。

    Android之從IO到NIO的模型機制實例分析

    當App寫入緩存數據的時候,直接映射到了內核緩存區,完成了磁盤的讀寫操作。

    1.2 Basic IO模型底層原理

    其實對于基礎的IO模型,也就是Basic IO的實現是阻塞的,其實我們也可以自己驗證,在主線程中進行讀寫操作就是阻塞的。

    那么對于IO來說,主要分為兩個階段:

    (1)數據準備階段;這里是由Java實現的,寫入到JVM中;

    (2)復制階段;內核空間復制用戶空間緩存數據,這部分需要調用內核函數(ioctl、sync),完成復制的工作。

    剩下的磁盤寫入操作就完全是由內核完成的,如果對于讀寫操作有疑問的,可以去看看下面這篇對于Binder底層原理的介紹。

    Android Framework原理 -- Binder驅動源碼分析

    對于傳統的Socket來說,這種屬于Net IO,本質也是阻塞性質的,例如App進程想要獲取一些數據,

    Android之從IO到NIO的模型機制實例分析

    上圖展示了read操作的整個調度過程:

    (1)當App調用系統方法想要獲取某些數據的時候,首先系統內核會等待數據從網絡中到達,這個過程內核處于阻塞的狀態

    (2)等到數據到達之后,就會將網絡數據復制到用戶空間的緩沖區中,并通知App進程復制數據成功,此時App中其他業務才能夠繼續執行。

    所以整個過程中,App處于阻塞狀態,而在高并發的場景中(客戶端很少,這里拿服務端來舉例),例如10000QPS(每秒10000次查詢操作),此時如果采用IO阻塞模型,帶來的后果就是CPU極速拉滿最終可能導致熔斷,所以針對這種情況,出現了NIO模型。

    2 NIO模型

    相對于IO模型來說,NIO模型做的優化是通過輪詢機制獲取內核的數據等待狀態,看下圖:

    Android之從IO到NIO的模型機制實例分析

    當一次詢問發出之后,如果當前內核還是數據等待狀態,那么內核空間會被”掛起“,此時App進程可以做其他的事情,等到下一次輪詢時間到了之后,再次發起詢問,如果此時已經拿到了數據,那么就會進行復制操作,將數據放入用戶進程緩沖區。

    Android之從IO到NIO的模型機制實例分析

    那么對此,java.nio包下提供了很多非阻塞IO的API,例如我們前面提到的MappedByteBuffer。其實還是前面我們探討的一個問題,在Android的場景下,很難碰到高并發的場景,所以基本上也很難用到這個,但是對于NIO模型的原理我們需要掌握透徹,在面試中可能會涉及到這些問題。

    3 OKIO

    最后介紹一個IO模型---OKIO,如果使用到OkHttp的伙伴們應該已經見到過這個,但是沒有實際地去研究,為啥要引入這個okio三方庫。

    首先okio是OkHttp團隊基于Basic IO研發的一套自己的IO體系,為啥要搞一個這個玩意出來呢?通過前面我們分析Basic IO存在的一些問題,首先 Basic IO是阻塞的,而且在客戶端端如果頻繁地進行網絡請求,而且網絡請求是雙向的,從客戶端發出請求,服務端返回響應,那么這個過程必定會使用到InputStream和OutputStream。

    因為OkHttp是有自己的緩存策略的,如果使用到緩存,那么對于InputStream就需要一個buffer,對于OutputStream也需要一個buffer,每次讀寫操作都需要兩個buffer來做支撐,因此針對這種場景,okio在底層做了處理。

    具體的處理就是不再使用byte[]數組存儲數據,而是采用Segment數據結構。有熟悉Segment的伙伴應該知道,它是一個數組的雙向鏈表,其中data就是一個byte數組,其中有next和pre兩個指針。

    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

    Android之從IO到NIO的模型機制實例分析

    當進行讀寫操作的時候,都會往Segment中寫入,就是將InputStream和OutputStream需要創建的緩沖區合并。

    這里需要說明一點,okio屬于OkHttp內部核心IO框架,并不是單獨拿出來任意業務方可以使用,所以對于okio的具體實現原理,后續會放在OkHttp框架原理中做詳細的介紹。

    感謝各位的閱讀,以上就是“Android之從IO到NIO的模型機制實例分析”的內容了,經過本文的學習后,相信大家對Android之從IO到NIO的模型機制實例分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    库伦旗| 富顺县| 鲜城| 怀宁县| 安阳县| 灌阳县| 剑川县| 眉山市| 贺州市| 金湖县| 太保市| 鲁甸县| 青田县| 吐鲁番市| 曲靖市| 邻水| 皮山县| 怀化市| 新晃| 汝南县| 定日县| 沿河| 抚远县| 唐河县| 迭部县| 陵水| 广河县| 宜兰市| 沧州市| 三都| 长宁县| 安阳市| 临邑县| 鸡泽县| 西贡区| 汨罗市| 宁化县| 赣榆县| 同德县| 通城县| 台东县|