亚洲国产日韩人妖另类,久久只有这里有精品热久久,依依成人精品视频在线观看,免费国产午夜视频在线

      
      

        Java NIO:從 Buffer、Channel、Selector 到 Zero-copy、I/O 多路復(fù)用

        Java NIO:從 Buffer、Channel、Selector 到 Zero-copy、I/O 多路復(fù)用

        NIO 是什么?

        nio 是 non-blocking 的簡稱,在 jdk1.4 里提供的新 api。Sun 官方標(biāo)榜的特性如下:為所有的原始類型提供(Buffer)緩存支持。字符集編碼解碼解決方案。Channel:一個新的原始 I/O 抽象。支持鎖和內(nèi)存映射文件的文件訪問接口。提供多路(non-blocking)非阻塞式的高伸縮性 I/O。

        NIO 實現(xiàn)高性能處理的原理是使用較少的線程來處理更多的任務(wù)。使用較少的 Thread 線程,通過 Selector 選擇器來執(zhí)行不同的 Channel 通道中的任務(wù),執(zhí)行的任務(wù)再結(jié)合 AIO(異步 I/O)就能發(fā)揮服務(wù)器最大的性能,大大提升軟件運行效率。

        Java NIO

        Java NIO 采用非阻塞高性能運行的方式來避免出現(xiàn)以前“笨拙”的同步I/O帶來的低效率問題。NIO在大文件操作上相比常規(guī)I/O更加優(yōu)秀。

        Buffer

        基礎(chǔ)知識點

        在使用傳統(tǒng)的 I/O 操作時,比如 InputStream/OutputStream ,通常是將數(shù)據(jù)暫存到 byte[] 或者 char[] 中,亦或者從 byte[] 或者 char[] 中來獲取數(shù)據(jù),但是在 Java 語言中對 array 數(shù)組自身提供的可操作的 API 非常少,常用的操作僅僅是 length 屬性和下標(biāo)[x],如果相對數(shù)組中的數(shù)據(jù)進(jìn)行更高級的操作,需要自己寫代碼來實現(xiàn),處理方式比較原始。而 Java NIO 中的 Buffer 類在暫存數(shù)據(jù)的同時還提供了很多工具方法,大大提高了程序開發(fā)效率。

        Buffer 是一個抽象類,用于存儲基本數(shù)據(jù)類型的容器,每個基本數(shù)據(jù)類型(除去 boolean )都有一個子類與之對應(yīng)。它具有 7 個直接子類:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer。

        注意:

        Buffer 類沒有 BooleanBuffer 這個子類。

        StringBuffer 在 java.lang 包下,而在 nio 包下并沒有,在 Nio 中存儲字符的緩沖區(qū)可以使用 CharBuffer 類。

        緩沖區(qū)為非線程安全的。

        在 Buffer 中有 4 個核心技術(shù)點:capacity、limit、position、mark。他們之間值的大小關(guān)系如下:

        0 <= mark <= position <= limit <= capacity

        • capacity:容量。代表該緩沖區(qū)當(dāng)前所能容納的元素的數(shù)量。不能為負(fù)數(shù),且不能更改。
        • limit:限制。代表第一個不應(yīng)該讀取或?qū)懭朐氐?index 索引。不能為負(fù)數(shù),且不能大于其 capacity。
        • position:位置。代表下一個將要讀取或?qū)懭朐氐?index 索引。不能為負(fù)數(shù),且不能大于其 limit 。如果新設(shè)置的 limit 小于 position,那么新的 limit 值就是 limit。
        • mark:標(biāo)記。緩沖區(qū)的標(biāo)記是一個索引,定義標(biāo)記時,不能將其定義為負(fù)數(shù),且不能大于其 position。標(biāo)記并不是必需的,如果定義了 mark,在調(diào)用 reset() 方法時,會將緩沖區(qū)的 position 重置為該標(biāo)記索引;在將 position 或者 limit 調(diào)整為小于該 mark 的值時,該 mark 會被丟棄,丟棄后 mark 的值時 -1。如果未定義 mark 調(diào)用 reset() 方法將導(dǎo)致拋出 invalidMarkException 異常。

        Buffer 中常用 API

        返回值

        方法名

        作用

        int

        capacity()

        返回此緩沖區(qū)的容量

        int

        limit()

        返回此緩沖區(qū)的限制

        Buffer

        limit(int newLimit)

        設(shè)置此緩沖區(qū)的限制

        int

        position()

        返回此緩沖區(qū)的位置

        Buffer

        position(int newPosition)

        設(shè)置此緩沖區(qū)的位置

        Buffer

        mark()

        在此緩沖區(qū)的位置設(shè)置標(biāo)記

        int

        remaining()

        返回當(dāng)前位置(position)與限制(limit)之間的元素個數(shù) return limit – position

        boolean

        hasRemaining()

        判斷在當(dāng)前位置和限制之間是否有元素。return position < limit

        boolean

        isReadOnly()

        返回此緩沖區(qū)是否為只讀緩沖區(qū)

        boolean

        isDirect()

        判斷此緩沖區(qū)是否為直接緩沖區(qū)

        Buffer

        clear()

        還原緩沖區(qū)到初始狀態(tài),包含將位置設(shè)置為 0,將限制設(shè)置為容量,丟棄標(biāo)記,即 “一切默認(rèn)”,但不會清除數(shù)據(jù)。 主要使用場景:在對緩沖區(qū)存儲數(shù)據(jù)之前調(diào)用此方法

        Buffer

        rewind()

        重繞此緩沖區(qū),將位置設(shè)置為0并丟棄標(biāo)記。 主要使用場景:常在重新讀取緩沖區(qū)數(shù)據(jù)時使用。

        Buffer

        flip()

        反轉(zhuǎn)此緩沖區(qū)。首先將限制設(shè)置為當(dāng)前位置,然后將位置設(shè)置為0。如果定義了標(biāo)記,則丟棄該標(biāo)記。 主要使用場景:當(dāng)向緩沖區(qū)存儲數(shù)據(jù),然后再從緩沖區(qū)讀取這些數(shù)據(jù)之前調(diào)用

        堆內(nèi)存與堆外內(nèi)存

        使用間接緩沖區(qū)(堆內(nèi)存)向硬盤存取數(shù)據(jù)時需要首先將數(shù)據(jù)復(fù)制暫存到 JVM 的中間緩沖區(qū)中,然后 Java 程序才能對數(shù)據(jù)進(jìn)行實際的讀寫操作。如果有頻繁操作數(shù)據(jù)的情況發(fā)生,會提高內(nèi)存占有率,大大降低軟件對數(shù)據(jù)的吞吐量。

        使用非間接緩沖區(qū)(堆外內(nèi)存)無需 JVM 創(chuàng)建新的中間緩沖區(qū),可直接在內(nèi)核空間完成數(shù)據(jù)的處理,這樣就減少了在 JVM 中創(chuàng)建緩沖區(qū)的步驟,增加了程序運行效率。

        處理數(shù)據(jù)常用操作

        以 ByteBuffer 為例,提供了 5 類操作。

      1. 以絕對位置和相對位置讀寫單個字節(jié)的 get() 和 put() 方法。
      2. 使用相對批量 get(byte[] dst) 方法將緩沖區(qū)中的連續(xù)字節(jié)讀取到 buty[] dst 目標(biāo)數(shù)組中;相對批量 put(byte[] src) 方法將 byte[] src 數(shù)組中或其他字節(jié)緩沖區(qū)中的連續(xù)字節(jié)存儲到此緩沖區(qū)中。
      3. 使用絕對和相對 getType 和 putType 方法可以按照字節(jié)順序在字節(jié)序列中讀寫其他基本數(shù)據(jù)類型的值,方法 getType 和 putType 可以進(jìn)行數(shù)據(jù)類型的自動轉(zhuǎn)換。
      4. 提供了創(chuàng)建視圖緩沖區(qū)的方法,這些方法允許將字節(jié)緩沖區(qū)視為包含其他基本類型值的緩沖區(qū),這些方法有:asCharBuffer()、asDoubleBuffer()、asFloatBuffer()、asIntBuffer()、asLongBuffer()、asShortBuffer()。
      5. 提供了對緩沖區(qū)進(jìn)行壓縮(compacting)、復(fù)制(duplicating)和截?。╯licing)的方法。
      6. 相對 / 絕對位置操作

        相對位置操作是指在讀取或?qū)懭胍粋€或多個元素時,它從“當(dāng)前位置開始”,然后將位置增加鎖傳輸?shù)脑貍€數(shù)。如果請求的傳輸超出限制,則相對 get 操作將拋出 BufferUnderflowException 異常,相對 put 操作將拋出 BufferOverflowException 異常,也就是說,在這兩種情況下 ,都沒有數(shù)據(jù)傳輸。

        絕對位置操作采用顯示元素索引,該操作不影響位置。如果索引參數(shù)超出限制,則絕對 get 操作和絕對 put 操作將拋出 IndexOutBoundsException 異常。

        返回值類型

        方法名

        作用

        Buffer

        put(byte b)

        將給定的字節(jié)寫入緩沖區(qū)的“當(dāng)前位置”。

        byte

        get()

        讀取此緩沖區(qū)“當(dāng)前位置”的字節(jié)。

        Buffer

        put(byte[] src, int offset, int length)

        把給定源數(shù)組中的字節(jié)寫入此緩沖區(qū)的“當(dāng)前位置中”。如果要從該數(shù)據(jù)中心復(fù)制的字節(jié)數(shù)多于此緩沖區(qū)中的剩余字節(jié)(即 length > remaining),則不傳輸字節(jié)且拋出 bufferOverflowException 異常。否則,將給定數(shù)組中的 length 個字節(jié)復(fù)制到此緩沖區(qū)中。將數(shù)組中給定 offset 偏移量位置的數(shù)據(jù)復(fù)制到此緩沖區(qū)的當(dāng)前位置,復(fù)制的元素個數(shù)為 length。

        byte[]

        get(byte[] dst, int offset, int length)

        將此緩沖區(qū)當(dāng)前位置的字節(jié)傳輸?shù)浇o定目標(biāo)數(shù)組中。如果此緩沖區(qū)中剩余的字節(jié)少于滿足請求所需要的字節(jié)(即 length > remaining),則不傳輸字節(jié)且拋出 BufferUnderflowWxception 異常。否則此方法將此緩沖區(qū)中的 length 個字節(jié)復(fù)制到給定數(shù)組中。從此緩沖區(qū)的當(dāng)前位置和數(shù)組中的給定偏移量位置開始復(fù)制。然后,此緩沖區(qū)的位置將增加 length。

        Buffer

        put(byte[] src)

        將給定的源 byte 數(shù)組的所有內(nèi)容存儲到此緩沖區(qū)的當(dāng)前位置。等同于:dst.put(a,0,a.length)

        byte[]

        get(byte[] dst)

        將緩沖區(qū) remaining 字節(jié)傳輸?shù)浇o定的目標(biāo)數(shù)組中。等同于:src.get(a,0,a.length)

        Buffer

        put(ByteBuffer src)

        相對批量 put 操作。將給定源緩沖區(qū)中的剩余字節(jié)傳輸?shù)酱司彌_區(qū)當(dāng)前位置中。如果源緩沖區(qū)中的剩余字節(jié)多于此緩沖區(qū)的剩余字節(jié),即 src.remaining() > remaining(),則不傳輸字節(jié)且拋出 BufferOverflowException 異常。兩個緩沖區(qū)的位置都會相應(yīng)遞增。

        Buffer

        put(int index, byte b)

        絕對 put 操作,將給定字節(jié)寫入此緩沖區(qū)的給定索引位置。

        byte

        get(int index)

        絕對 get 操作,讀取指定位置索引處的字節(jié)。

        getType / putType 操作

        可以直接根據(jù)源基本數(shù)據(jù)類型將數(shù)據(jù)寫入此緩沖區(qū),或者從此緩沖區(qū)讀取指定類型的數(shù)據(jù),同時此為緩沖區(qū)的位置位置根據(jù)不同的數(shù)據(jù)類型所占的字節(jié)數(shù)做相應(yīng)的增加。

        返回值類型

        方法名

        作用

        ByteBuffer

        putChar(char value)

        相對操作,將給定 char 值按照當(dāng)前字節(jié)順序?qū)懭氲酱司彌_區(qū)的當(dāng)前位置,然后將此緩沖區(qū)位置增加 2,因為一個字符占 2 個字節(jié)。

        ByteBuffer

        putChar(int index, char value)

        絕對操作,將給定 char 值按照當(dāng)前字節(jié)順序?qū)懭氲酱司彌_區(qū)的給定位置。

        ByteBuffer

        putDouble(double value)

        相對操作,將給定 double 值按照當(dāng)前字節(jié)順序?qū)懭氲酱司彌_區(qū)的當(dāng)前位置,然后將此緩沖區(qū)位置增加 8,因為一個 double 類型占 8 個字節(jié)。

        ByteBuffer

        putDouble(int index, double value)

        絕對操作,將給定 double 值按照當(dāng)前字節(jié)順序?qū)懭氲酱司彌_區(qū)的給定位置。

        ByteBuffer

        putFloat(float value)

        相對操作,將給定 float 值按照當(dāng)前字節(jié)順序?qū)懭氲酱司彌_區(qū)的當(dāng)前位置,然后將此緩沖區(qū)位置增加 4,因為一個float 類型占 4 個字節(jié)。

        ByteBuffer

        putFloat(int index, float value)

        絕對操作,將給定 float 值按照當(dāng)前字節(jié)順序?qū)懭氲酱司彌_區(qū)的給定位置。

        ByteBuffer

        put(int value)

        相對操作,將給定 int 值按照當(dāng)前字節(jié)順序?qū)懭氲酱司彌_區(qū)的當(dāng)前位置,然后將此緩沖區(qū)位置增加 4,因為一個 int 類型占 4 個字節(jié)。

        ByteBuffer

        put(int index, int value)

        絕對操作,將給定 int 值按照當(dāng)前字節(jié)順序?qū)懭氲酱司彌_區(qū)的給定位置。

        ByteBuffer

        put(long value)

        相對操作,將給定 long 值按照當(dāng)前字節(jié)順序?qū)懭氲酱司彌_區(qū)的當(dāng)前位置,然后將此緩沖區(qū)位置增加 8,因為一個 long 類型占 8 個字節(jié)。

        ByteBuffer

        put(int index, long value)

        絕對操作,將給定 long 值按照當(dāng)前字節(jié)順序?qū)懭氲酱司彌_區(qū)的給定位置。

        ByteBuffer

        putShort(short value)

        相對操作,將給定 short 值按照當(dāng)前字節(jié)順序?qū)懭氲酱司彌_區(qū)的當(dāng)前位置,然后將此緩沖區(qū)位置增加 2,因為一個 short 類型占 2 個字節(jié)。

        ByteBuffer

        putShort(int index, short value)

        絕對操作,將給定 short 值按照當(dāng)前字節(jié)順序?qū)懭氲酱司彌_區(qū)的給定位置。

        緩沖區(qū)類型轉(zhuǎn)換

        通過調(diào)用 asXXXBuffer() 方法,將源字節(jié)緩沖區(qū)轉(zhuǎn)換成特定類型的緩沖區(qū)。新緩沖區(qū)的內(nèi)容將從此緩沖區(qū)的當(dāng)前位置開始。此緩沖區(qū)內(nèi)容的更改,在新緩沖區(qū)中是可見的,反之亦然。這兩個緩沖區(qū)的位置、限制和標(biāo)記值是相互獨立的。新緩沖區(qū)的位置將為 0,其容量和限制與所轉(zhuǎn)換的視圖緩沖區(qū)類型有關(guān),比如,將字節(jié)緩沖區(qū)通過 asCharBuffer() 方法轉(zhuǎn)換成字符緩沖區(qū),那么新的字符緩沖區(qū)的容量和限制將為源字節(jié)緩沖區(qū)中所剩字節(jié)數(shù)的 1/2,其標(biāo)記時不確定的。當(dāng)且僅當(dāng)源緩沖區(qū)為直接緩沖區(qū)時,新緩沖區(qū)才是直接緩沖區(qū);當(dāng)且僅當(dāng)源緩沖區(qū)是只讀緩沖區(qū)時,新緩沖區(qū)才是只讀緩沖區(qū)。

        注意:

        當(dāng)緩沖區(qū)類型轉(zhuǎn)換后,再讀取時,需要注意其讀寫時的編碼,如果編碼不一致,會導(dǎo)致中文亂碼。解決辦法就是調(diào)整在轉(zhuǎn)換前后的讀寫編碼一致。

        返回值類型

        方法名

        作用

        CharBuffer

        asCharBuffer()

        創(chuàng)建此字節(jié)緩沖區(qū)的視圖,作為 char 緩沖區(qū)。新的字符緩沖區(qū)的容量和限制將為源字節(jié)緩沖區(qū)中所剩字節(jié)數(shù)的 1/2

        DoubleBuffer

        asDoubleBuffer()

        創(chuàng)建此字節(jié)緩沖區(qū)的視圖,作為 double 緩沖區(qū)。新的字符緩沖區(qū)的容量和限制將為源字節(jié)緩沖區(qū)中所剩字節(jié)數(shù)的 1/8

        FloatBuffer

        asFloatBuffer()

        創(chuàng)建此字節(jié)緩沖區(qū)的視圖,作為 float 緩沖區(qū)。新的字符緩沖區(qū)的容量和限制將為源字節(jié)緩沖區(qū)中所剩字節(jié)數(shù)的 1/4

        IntBuffer

        asIntBuffer()

        創(chuàng)建此字節(jié)緩沖區(qū)的視圖,作為 int 緩沖區(qū)。新的字符緩沖區(qū)的容量和限制將為源字節(jié)緩沖區(qū)中所剩字節(jié)數(shù)的 1/4

        LongBuffer

        asLongBuffer

        創(chuàng)建此字節(jié)緩沖區(qū)的視圖,作為 long 緩沖區(qū)。新的字符緩沖區(qū)的容量和限制將為源字節(jié)緩沖區(qū)中所剩字節(jié)數(shù)的 1/8

        ShortBuffer

        asShortBuffer()

        創(chuàng)建此字節(jié)緩沖區(qū)的視圖,作為 float 緩沖區(qū)。新的字符緩沖區(qū)的容量和限制將為源字節(jié)緩沖區(qū)中所剩字節(jié)數(shù)的 1/2

        只讀緩沖區(qū)

        通過 asReadOnlyBuffer() 方法創(chuàng)建共享此緩沖區(qū)內(nèi)容的只讀緩沖區(qū)。新緩沖區(qū)的內(nèi)容將為此緩沖區(qū)的內(nèi)容。此緩沖區(qū)內(nèi)容的更改在新緩沖區(qū)中是可見的,但是新緩沖區(qū)是只讀,不允許修改共享內(nèi)容。兩個緩沖區(qū)的位置、限制和標(biāo)記值是相互獨立的。新緩沖區(qū)的容量、限制、位置和標(biāo)記值將于此緩沖區(qū)相同。

        壓縮緩沖區(qū)

        將緩沖區(qū)的當(dāng)前位置和限制之間的字節(jié)(如果有)復(fù)制到緩沖區(qū)的開始處。即將所有 p = position() 處的字節(jié)復(fù)制到索引 0 處,將索引 p+1 處的字節(jié)復(fù)制到索引 1 處,以此類推,直到將索引 limit() – 1 處的字節(jié)復(fù)制到索引 n = limit() – 1 – p 處。然后,將緩沖區(qū)的位置設(shè)置為 n + 1 ,并且將其限制設(shè)置為其容量。如果已定義了標(biāo)記,則丟棄它。

        // 1. 緩沖區(qū)中的內(nèi)容|1|2|3|4|5|6|7|8|9|// 2. 執(zhí)行讀取操作到索引 3 處|1|2|3|>4|5|6|7|8|9|// 3. 經(jīng)過 compact 壓縮后緩沖區(qū)數(shù)據(jù)內(nèi)容為|4|5|6|7|8|9|7|8|9|

        復(fù)制緩沖區(qū)

        通過 duplicate() 方法創(chuàng)建共享此緩沖區(qū)內(nèi)容的新的緩沖區(qū)。新緩沖區(qū)的內(nèi)容將為此緩沖區(qū)的內(nèi)容。此緩沖區(qū)內(nèi)容的更改在新緩沖區(qū)中是可見的,反之亦然。在創(chuàng)建新緩沖區(qū)時,容量、限制、位置和標(biāo)記值將與此緩沖區(qū)相同,但是這兩個緩沖區(qū)的位置、限制和標(biāo)記值是相互獨立的。當(dāng)且僅當(dāng)此緩沖區(qū)為直接緩沖區(qū)時,新緩沖區(qū)才是直接緩沖區(qū);當(dāng)且僅當(dāng)此緩沖區(qū)為只讀緩沖區(qū)時,新緩沖區(qū)才是只讀緩沖區(qū)。

        截取緩沖區(qū)

        通過 slice() 方法創(chuàng)建新的字節(jié)緩沖區(qū),其內(nèi)容是此緩沖區(qū)內(nèi)容的共享子序列。新的緩沖區(qū)內(nèi)容將從此緩沖區(qū)的當(dāng)前位置開始。此緩沖區(qū)內(nèi)容的更改在新緩沖區(qū)中是可見的,反之亦然。這兩個緩沖區(qū)的位置、限制和標(biāo)記是相互獨立的。新緩沖區(qū)的位置將為 0,其容量和限制為此緩沖區(qū)中所剩余的字節(jié)數(shù)量,標(biāo)記是不確定的。當(dāng)且僅當(dāng)此緩沖區(qū)為直接緩沖區(qū)時,新緩沖區(qū)才是直接緩沖區(qū);當(dāng)且僅當(dāng)此緩沖區(qū)為只讀緩沖區(qū)時,新緩沖區(qū)才是只讀緩沖區(qū)。

        比較緩沖區(qū)的內(nèi)容

        比較緩沖區(qū)內(nèi)容是否相同有兩種方法:equals() 和 compareTo()。這兩種方法還是有使用細(xì)節(jié)上的區(qū)別。

        public boolean equals(Object ob) { // 1. 如果比較的是同一個對象,則直接返回 true if (this == ob) return true; // 2. 如果所比較的對象非 ByteBuffer 類型對象,直接返回 false if (!(ob instanceof ByteBuffer)) return false; // 3. 將所比較的對象轉(zhuǎn)換成 ByteBuffer 類型,然后比較兩者剩余元素個數(shù),即 remaing 值,如果不相等,直接返回 false ByteBuffer that = (ByteBuffer)ob; if (this.remaining() != that.remaining()) return false; // 4. 倒敘逐個比較兩個 ByteBuffer 對象剩余元素是否相同,如果有一個不同,則直接返回 false。 int p = this.position(); for (int i = this.limit() – 1, j = that.limit() – 1; i >= p; i–, j–) if (!equals(this.get(i), that.get(j))) return false; return true;}

        從源碼中可以看出 equals() 方法比較的是兩個 ByteBuffer 對象中剩余元素是否相等,包括個數(shù)及每個元素的序列值。而兩個緩沖區(qū)的容量可以不同。

        public int compareTo(ByteBuffer that) { /** * 1.在此緩沖區(qū)的基礎(chǔ)上,計算需要比較的元素終點位置。 * 以當(dāng)前位置為起點,加上兩個 ByteBuffer 對象最小的剩余元素個數(shù)為終點 * 說明判斷范圍是兩個 ByteBuffer 對象的 remaining 的交集 **/ int n = this.position() + Math.min(this.remaining(), that.remaining()); // 2. 正序比較兩個 ByteBuffer 對象 remaining 交集中的元素是否相同,如果有一個不同,返回兩者的差值 thisIndex – thatIndex for (int i = this.position(), j = that.position(); i < n; i++, j++) { int cmp = compare(this.get(i), that.get(j)); if (cmp != 0) return cmp; } // 3. 如果交集中的元素都相同,那么比較兩個 ByteBuffer 對象的 remaining 元素個數(shù),返回兩者的差值 thisRemaining – thatRemaining return this.remaining() – that.remaining();}

        源碼可以看出 compareTo 方法也是比較的兩個 ByteBuffer 對象的剩余元素,只不過返回的是某個字節(jié)序列的差值或者兩個 remaining 的差值。與兩個緩沖區(qū)的容量無關(guān),這一點與 equals() 方法一致。

        • 如果兩個 ByteBuffer 對象的 remaining 交集中有一個元素的序列值不相等,那么返回他們的差值。
        • 如果兩個 ByteBuffer 對象的 remaining 交集中的所有元素的序列值都相等,在進(jìn)行比較兩個 ByteBuffer 對象的 remaining 個數(shù),并返回他們的差值。

        Channel

        緩沖區(qū)是將數(shù)據(jù)進(jìn)行打包,而通道是將數(shù)據(jù)進(jìn)行傳輸。緩沖區(qū)是類,而通道都是接口,因為通道的功能實現(xiàn)是要依賴操作系統(tǒng)的,Channel 接口只定義有哪些功能,而功能的具體實現(xiàn)在不同的操作系統(tǒng)中是不一樣的。

        通道是用于 IO 操作的連接,可處于打開或關(guān)閉兩種狀態(tài),當(dāng)創(chuàng)建通道時,通道就處于打開狀態(tài),一旦將其關(guān)閉,則保持關(guān)閉狀態(tài)。通過 isOpen() 方法可以測試通道是否處于打開狀態(tài),避免出現(xiàn) ClosedChannelException 異常。

        Channel 接口類圖結(jié)構(gòu)

        Channel接口類圖

        AutoCloseable 接口

        AutoCloseable 接口的作用是可以自動關(guān)閉,而不需要顯示地調(diào)用 close() 方法。AutoCloseable 接口強調(diào)的是與 try() 結(jié)合實現(xiàn)自動關(guān)閉。該接口之定義了一個 close() 方法,因為針對的是任何資源的關(guān)閉,而不只是 I/O,因此 close() 方法拋出的是 Exception 異常。而且該接口不要求是冪等的,也就是重復(fù)調(diào)用此接口的 close() 方法會出現(xiàn)副作用。

        public class DBOperate implements AutoCloseable { @Override public void close() throws Exception { System.out.println(“關(guān)閉連接”); }}public class Test { public static void main(String[] args){ try (DBOprate dbo = new DBOprate()){ System.out.println(“開始數(shù)據(jù)庫操作”); }catch (Exception e){ e.printStackTrace(); } }}//輸出結(jié)果:開始數(shù)據(jù)庫操作關(guān)閉連接

        Closeable 接口

        Closeable 接口繼承自 AutoCloseable 接口,其作用是關(guān)閉 I/O 流,釋放系統(tǒng)資源,所以該接口的 close() 方法拋出 IOException 異常。該接口的 close() 方法是冪等的,可以重復(fù)調(diào)用此接口的 close() 方法,而不會出現(xiàn)任何效果與影響。

        AsynchronousChannel 接口

        主要作用是使通道支持異步 I/O 操作。異步 I/O 操作有以下兩種方式進(jìn)行實現(xiàn):

        • 方法Future operation(…)Future 對象可以用于檢測 I/O 操作是否完成,或者等待完成,以及用于接收 I/O 操作處理后的結(jié)果。但是需要開發(fā)人員編寫檢測邏輯。
        • 回調(diào)void operation(… A attachment, CompletionHandler handler)A 類型的對象 attachment 的主要作用是讓外部與 CompletionHandler 對象內(nèi)部進(jìn)行通信。有點是 CompletionHandler 對象可以被復(fù)用,當(dāng) I/O 操作成功或失敗時,CompletionHandler 對象中的指定方法會自動被調(diào)用,不需要開發(fā)人員編寫檢測的邏輯。

        異步通道在多線程并發(fā)的情況下是線程安全的。某些通道的實現(xiàn)是可以支持并發(fā)讀和寫的,但是不允許在一個未完成的 I/O 操作上再次調(diào)用 read 或 wwrite 操作。

        異步通道支持取消操作,通過調(diào)用 Future 接口定義的 cancel() 方法來取消執(zhí)行,這會導(dǎo)致那些等待處理 I/O 結(jié)果的線程拋出 CancellationException 異常。

        AsynchronousByteChannel 接口

        主要作用是使通道支持異步 I/O 操作,操作單位為字節(jié)。在上一個 read() 或 write() 方法未完成之前再次調(diào)用,會拋出 ReadPendingException 或者 WritePendingException 異常。

        ByteBuffer 類不是線程安全的,盡量保證在對其進(jìn)行讀寫操作時,沒有其他線程一同進(jìn)行讀寫操作。

        ReadableByteChannel 接口

        主要作用是使通道運行對字節(jié)進(jìn)行讀操作。該接口只允許有 1 個讀操作在進(jìn)行,如果 1 個線程正在 1 個通道上執(zhí)行 1 個 read() 操作,那么任何試圖發(fā)起另一個 read() 操作的線程都會被阻塞,直到第 1 個 read() 操作完成。即該接口的 read() 方法是同步的。

        該通道只接受以字節(jié)為單位的數(shù)據(jù)處理,因為通道和操作系統(tǒng)進(jìn)行交互時,操作系統(tǒng)只接受字節(jié)數(shù)據(jù)。

        ScatteringByteChannel 接口

        主要作用是可以從通道中讀取字節(jié)到多個緩沖區(qū)中。

        WritableByteChannel 接口

        主要作用是使通道運行對字節(jié)進(jìn)行寫操作。將字節(jié)緩沖區(qū)中的字節(jié)序列寫入到通道的當(dāng)前位置,該接口只允許有 1 個寫操作在進(jìn)行,如果 1 個線程正在 1 個通道上執(zhí)行 1 個 write() 操作,那么任何試圖發(fā)起另一個 write() 操作的線程都會被阻塞,直到第 1 個 write() 操作完成。即該接口的 write() 方法是同步的。

        GatheringByteChannel 接口

        主要作用是可以將多個緩沖區(qū)中的數(shù)據(jù)寫入到通道中。

        ByteChannel 接口

        主要作用是將 ReadableByteChannel(可讀字節(jié)通道)與 WritableByteChannel(可寫字節(jié)通道)的規(guī)范進(jìn)行了統(tǒng)一。ByteChannel 沒有添加任何新的方法就實現(xiàn)了具有讀和寫的功能,是雙向的操作。

        SeekableByteChannel 接口

        主要作用是在字節(jié)通道中維護 position,以及允許 position 發(fā)生改變。

        NetworkChannel 接口

        主要作用是使通道與 Socket 進(jìn)行關(guān)聯(lián),使通道中的數(shù)據(jù)能在 Socket 技術(shù)上進(jìn)行傳輸。

        MulticastChannel 接口

        主要作用是使通道支持 Internet Protocol(IP) 多播。也就是將多個主機地址進(jìn)行打包,形成一個組(group),然后將 IP 報文向這個組進(jìn)行發(fā)送,也就相當(dāng)于同時向多個主機傳輸數(shù)據(jù)。

        InterruptibleChannel 接口

        主要作用是使通道能以異步的方式進(jìn)行關(guān)閉與中斷。

        FileChannel 類的使用

        以 FileChannel 為例來介紹下通道(Channel)的一般常用操作,不同的通道雖然 Channel 類型不同,但是在程序中所起到的作用是相同的,再結(jié)合上述的接口類圖,根據(jù)不同類型的 Channel 接口實現(xiàn)可以實現(xiàn)特定的功能。

        FileChannel類圖

        通過類圖分析得知 FileChannel 是一個可以讀取、寫入、可中斷和操作文件的通道:

        • 實現(xiàn)了 WritebleByteChannel 和 ReadableByteChannel 類型的接口,說明支持讀和寫操作;
        • 繼承了 AbstractInterruptibleChannel 類,說明是一個可中斷的通道;
        • 沒有實現(xiàn) AsynchronousChannel 類型的接口,所以 FileChnnel 不支持異步,永遠(yuǎn)是阻塞的操作;

        FileChannel 在內(nèi)部維護當(dāng)前文件的 position ,可對其進(jìn)行查詢和修改。該文件本身包含一個可讀寫、長度可變的字節(jié)序列,并且可以查詢該文件的當(dāng)前大小。當(dāng)寫入的字節(jié)超出文件的當(dāng)前大小時,則增加文件的大小;截取該文件時,則減小文件的大?。坏祟愇炊x訪問元數(shù)據(jù)的方法,所以無法訪問文件的元數(shù)據(jù),比如:權(quán)限、內(nèi)容類型和最后修改時間等。

        除了通道常見的讀、寫和關(guān)閉操作外,此類還定義了下列特定于文件的操作:

      7. 以不影響通道當(dāng)前位置的方式,對文件中絕對位置的字節(jié)進(jìn)行讀取或?qū)懭耄?/li>
      8. 將文件中的某個區(qū)域直接映射到內(nèi)存中。對于較大的文件,這通常比調(diào)用普通的 read() 或 write() 方法更有效;
      9. 強制對底層存儲設(shè)備進(jìn)行文件的更新,確保在系統(tǒng)崩潰時不丟失數(shù)據(jù);
      10. 以一種可被很多操作系統(tǒng)優(yōu)化為直接向文件系統(tǒng)緩存發(fā)送或從中讀取的高速傳輸方法,將字節(jié)從文件傳輸?shù)侥硞€其他通道中,反之亦然;
      11. 可以鎖定某個文件區(qū)域,以阻止其他程序?qū)ζ溥M(jìn)行訪問;
      12. FileChannel 類沒有定義打開現(xiàn)有文件通道或創(chuàng)建新文件通道的方法,可通過調(diào)用現(xiàn)有的 FileInputStream、FileOutputStream、或 RandomAccessFile 對象的 getChannel() 方法來獲得。

        • 通過 FiltInputStream 實例的 getChannel() 方法獲得的通道將允許進(jìn)行讀取操作;
        • 通過 FileOutputStream 實例的 getChannel() 方法獲得的通道將允許進(jìn)行寫入操作,如果輸出流對象是通過 FileOutputStream(File,boolean) 構(gòu)造方法且第二個參數(shù)傳入 true 創(chuàng)建的,則該通道模式可能處于添加模式,每次調(diào)用相關(guān)的寫入操作都會首先將位置移動到文件的末尾,然后寫入請求的數(shù)據(jù);
        • 通過調(diào)用通過 r 模式創(chuàng)建的 RandomAccessFile 實例的 getChannel() 方法獲得的通道將允許進(jìn)行讀取操作,RandomAccessFile 實例是通過 rw 模式創(chuàng)建,那么獲得的通道將允許進(jìn)行讀取和寫入操作;

        返回值類型

        方法名

        作用

        int

        write(ByteBuffer src)

        同步方法,將給定 ByteBuffer 的 remaining 字節(jié)序列寫入到通道的當(dāng)前位置;

        int

        read(ByteBuffer dst)

        同步方法,將字節(jié)序列從通道的當(dāng)前位置讀入給定的緩沖區(qū)的當(dāng)前位置,如果該通道已到達(dá)流的末尾,則返回 -1;正數(shù) – 代表讀入緩沖區(qū)的字節(jié)個數(shù);0 – 代表從通道中沒有讀取任何字節(jié),可能發(fā)生的情況就是緩沖區(qū)中沒有 remaining 剩余空間了;-1 – 代表到達(dá)流的末端;

        long

        write(ByteBuffer[] srcs)

        同步方法,將給定的緩沖區(qū)數(shù)組中的每個緩沖區(qū)的 remaining 字節(jié)序列寫入到通道的當(dāng)前位置;

        long

        read(ByteBuffer[] dsts)

        同步方法,從此通道當(dāng)前位置開始將通道中剩余的字節(jié)序列,讀入到多個給定的字節(jié)緩沖區(qū)中;如果通道中可讀出來的數(shù)據(jù)大于 ByteBuffer[] 緩沖區(qū)組總共的容量,那么 ByteBuffer[] 緩沖區(qū)組總共的容量多少,就讀取多少字節(jié)的數(shù)據(jù)

        long

        write(ByteBuffer[] srcs, int offset, int length)

        同步方法,以指定緩沖區(qū)數(shù)組的 offset 下標(biāo)開始,向后使用 length 個字節(jié)緩沖區(qū),再將每個緩沖區(qū)的 remaining 剩余字節(jié)序列寫入到此通道的當(dāng)前位置;

        long

        read(ByteBuffer[] dsts, int offset, int length)

        同步方法,將通道中當(dāng)前位置的字節(jié)序列讀入以下標(biāo)為 offset 開始的 ByteBuffer[] 數(shù)組中的 remaining 剩余空間中,并且連續(xù)寫入 length 個 ByteBuffer 緩沖區(qū);

        int

        write(ByteBuffer src, long position)

        將緩沖區(qū)的 remaining 字節(jié)序列寫入通道的指定位置;如果給定的位置大于該文件的當(dāng)前大小,則該文件將擴大以容納新的字節(jié),在文件末尾和新寫入字節(jié)之間的字節(jié)值是未指定的;該方法不影響此通道的當(dāng)前位置;

        int

        read(ByteBuffer dst, long position)

        將通道的指定位置的字節(jié)序列讀入給定的緩沖區(qū)的當(dāng)前位置;如果給定的位置大于該文件的當(dāng)前大小,則不讀取任何字節(jié);該方法不影響此通道的當(dāng)前位置;

        long

        position(long newPosition)

        設(shè)置此通道的當(dāng)前位置。當(dāng)設(shè)置為大于當(dāng)前文件大小的值時,并不會改變文件的大小,稍后試圖在這樣的位置讀取字節(jié)將立即返回已到達(dá)文件末尾的指示,稍后試圖在這樣的位置寫入字節(jié)將導(dǎo)致文件擴大,以容納新的字節(jié),在原來的文件位置和新設(shè)置的文件位置之間的字節(jié)值時未指定的;

        long

        size()

        返回此通道所關(guān)聯(lián)文件的當(dāng)前大小;

        FileChannel

        truncate(long sieze)

        將此通道所關(guān)聯(lián)文件截取為給定大小。如果給定大小小于該文件的當(dāng)前大小,則截取該文件,丟棄文件新末尾后面的所有字節(jié);如果給定大小大于或等于該文件的當(dāng)前大小,則不修改文件;如果該通道的位置大于給定的大小,則將位置設(shè)置為給定的大??;

        long

        transferTo(long position, long count, WritableByteChannel dest)

        將字節(jié)從此通道的文件傳輸?shù)浇o定的可寫入字節(jié)通道。讀取從此通道的文件中給定 position 處開始的 count 個字節(jié),并將其寫入目標(biāo)通道的當(dāng)前位置。如果此通道的文件從給定的 position 處開始包含的字節(jié)數(shù)小于 count 個字節(jié),或者如果目標(biāo)通道是非阻塞的并且其輸出緩沖區(qū)中的自由空間少于 count 個字節(jié),則所傳輸?shù)淖止?jié)數(shù)小于請求的字節(jié)數(shù);該方法不影響此通道的當(dāng)前位置;如果給定的 position 位置大于當(dāng)前文件的大小,則不傳輸任何字節(jié);

        long

        transferFrom(ReadableByteChannel src, long position, long count)

        將字節(jié)從給定的可讀取字節(jié)通道傳輸?shù)酱送ǖ赖奈募?。試著從源通?src 中最多讀取 count 個字節(jié),并將其寫入到此通道的文件中從給定的 position 處開始的位置;如果源通道的剩余空間小于 count 個字節(jié),或者如果源通道是非阻塞的并且其輸入緩沖區(qū)中直接可用的空間小于 count 個字節(jié),則所傳輸?shù)淖止?jié)數(shù)要小于請求的字節(jié)數(shù);如果給定的位置大于該文件的當(dāng)前大小,則不傳輸任何字節(jié);該方法不影響此通道的當(dāng)前位置;

        FileLock

        lock(long position, long size, boolean shared)

        同步方法,獲取此通道的文件給定區(qū)域上的鎖定。在可以鎖定該區(qū)域之前、已關(guān)閉此通道之前或者已中斷調(diào)用線程之前(以先到者為準(zhǔn)),將阻塞此方法的調(diào)用;在此方法調(diào)用期間,如果另一個線程關(guān)閉了此通道,則拋出 AsynchronousCloseException 異常;如果在等待獲取鎖定的同時中斷了調(diào)用線程,則將狀態(tài)設(shè)置為中斷并拋出 FileLockInterruptionException 異常。如果調(diào)用此方法時已設(shè)置調(diào)用方的中斷狀態(tài),則立即拋出該異常;不更改線程的中斷狀態(tài);lock() 方法只鎖定大小為 Long.MAX_VALUES 的區(qū)域;文件鎖要么是獨占的,要么是共享的。共享鎖定可以阻止其他并發(fā)運行的程序獲取重疊的獨占鎖定,但是允許該程序獲取重疊的共享鎖定。獨占鎖定則阻止其他程序獲取共享或獨占類型的重疊鎖定;某些操作系統(tǒng)不支持共享鎖定,這種情況下,自動將對共享鎖定的請求轉(zhuǎn)換為對獨占鎖定的請求;

        FileLock

        tryLock(long position, long size, boolean shared)

        試圖獲取對此通道的文件給定區(qū)域的鎖定。此方法不會阻塞,無論是否成功獲取請求區(qū)域上的鎖定,都會立即返回;如果由于另一個程序保持著一個重疊鎖定而無法獲取鎖定,此方法返回 null,其他原因則拋出異常;

        void

        force(boolean metaData)

        強制將所有對此通道的文件更新寫入包含該文件的存儲設(shè)備中。如果此通道的文件駐留在本地存儲設(shè)備上,此方法返回時可以保證:在此通道創(chuàng)建后或在最后一次調(diào)用此方法后,對該文件進(jìn)行的所有更改都寫入存儲設(shè)備中。這對確保在系統(tǒng)崩潰時不會丟失重要信息特別有用。如果該文件不再本地設(shè)備商,則無法提供這樣的保證。metaData 參數(shù)可用于限制此方法是否必須更新文件元數(shù)據(jù)信息,fase 表示對文件內(nèi)容的更新寫入存儲設(shè)備; true 表示必須寫入對文件內(nèi)容和元數(shù)據(jù)的更新,這通常需要一個以上的 I/O 操作。此參數(shù)是否有效取決于底層操作系統(tǒng);調(diào)用此方法可能導(dǎo)致發(fā)送 I/O 操作,即使該通道僅允許進(jìn)行讀取操作時也是如此,例如,某些操作系統(tǒng)將最后一次訪問的時間作為元數(shù)據(jù)的一部分進(jìn)行維護,每當(dāng)讀取問件時就更新此時間,但實際是否會執(zhí)行 I/O 操作還是與操作系統(tǒng)相關(guān);該方法只能保證強制進(jìn)行通過此類中已定義的方法對此通道的文件所進(jìn)行的更改,而不一定強制那些通過修改已映射字節(jié)緩沖區(qū)的內(nèi)容所進(jìn)行的更改。

        FileLock 類具有平臺依賴性,此文件鎖定 API 直接映射到底層操作系統(tǒng)的本機鎖定機制。因此,無論程序使用何種語言編寫的,某個文件上所保持的鎖定對于所有訪問該文件的程序來說都應(yīng)該是可見的。

        關(guān)于 force(boolean metaData) 強制更新

        其實在調(diào)用 FileChannel 類的 Write() 方法時,操作系統(tǒng)為了運行的效率,顯示把那些將要保存到硬盤上的數(shù)據(jù)暫時放入操作系統(tǒng)的緩存中,以減少硬盤的讀寫次數(shù),然后在某一個時間點再將內(nèi)核緩存中的數(shù)據(jù)批量地同步到硬盤中,但同步的時間卻是由操作系統(tǒng)決定的。通過 force(boolean metaData) 強制進(jìn)行同步,這樣做的目的是防止在系統(tǒng)崩潰或斷電時緩存中的數(shù)據(jù)丟失而造成損失。但是,force(boolean metaData) 方法并不能完全保證數(shù)據(jù)不丟失,如果正在執(zhí)行 force(boolean metaData) 方法時出現(xiàn)斷電的情況,那么硬盤上的數(shù)據(jù)有可能就不是完整的,而且由于斷電的原因?qū)е聝?nèi)核緩存中的數(shù)據(jù)也丟失了,最終造成的結(jié)果就是 force(boolean metaData) 方法執(zhí)行了,數(shù)據(jù)也有可能丟失。所以,force(boolean metaData) 方法的最終目的是盡最大的努力減少數(shù)據(jù)的丟失。

        內(nèi)存映射

        通過 MappedByteBuffer map(FileChannel.MapMode mode, long position, long size) 方法可以將此通道的文件區(qū)域直接映射到內(nèi)存中。

        映射模式有以下 3 種:

      13. 只讀(MapMode.READ_ONLY):試圖修改得到的緩沖區(qū)將拋出 ReadOnlyBufferException 異常。
      14. 讀取/寫入(MapMode.READ_WRITE):對得到的緩沖區(qū)的更改最終將傳播到文件;該更改對映射到同一文件的其他程序不一定是可見的。
      15. 專用(MapMode.PRIVATE):對得到的緩沖區(qū)的更改不會傳播到文件,并且該更改對映射到同一文件的其他程序也是不可見的;相反,會創(chuàng)建緩沖區(qū)已修改部分的專用副本。
      16. 對于只讀映射關(guān)系,此通道必須可以進(jìn)行讀取操作;對于讀取/寫入或?qū)S糜成潢P(guān)系,此通道必須可以進(jìn)行讀取和寫入操作。

        映射關(guān)系一經(jīng)創(chuàng)建,就不再依賴于創(chuàng)建它時所用的文件通道。特別是關(guān)閉該通道對映射關(guān)系的有效性沒有任何影響。

        對于大多數(shù)操作系統(tǒng)而言,與通過普通的 read() 和 write() 方法讀取或?qū)懭霐?shù)千字節(jié)的數(shù)據(jù)相比,將文件映射到內(nèi)存中開銷更大。從性能的觀點來看,通常將相對較大的文件映射到內(nèi)存中才是值得的。

        MappedByteBuffer 類的 force() 方法的作用是將此緩沖區(qū)所做的更改強制寫入包含映射文件的存儲設(shè)備中。如果此緩沖區(qū)不是以讀/寫模式映射的,則調(diào)用此方法無效。

        零拷貝

        零拷貝(Zero-copy)技術(shù)是指計算機執(zhí)行操作時,CPU 不需要先將數(shù)據(jù)從某處內(nèi)存復(fù)制到另一個特定區(qū)域。這種技術(shù)通常用于通過網(wǎng)絡(luò)傳輸文件時節(jié)省CPU周期和內(nèi)存帶寬。——百度百科

        如果要讀取一個文件并通過網(wǎng)絡(luò)發(fā)送它,傳統(tǒng)方式下每個讀/寫周期都需要復(fù)制兩次數(shù)據(jù)和切換兩次上下文(用戶空間和內(nèi)核空間之間的切換),而數(shù)據(jù)的復(fù)制都需要依靠CPU。通過零復(fù)制技術(shù)完成相同的操作,上下文切換減少到兩次,并且不需要CPU復(fù)制數(shù)據(jù)。

        傳統(tǒng)I/O操作

        BIO

        • 讀虛擬機會從用戶空間向內(nèi)核空間發(fā)起一個讀命令的系統(tǒng)調(diào)用,由用戶空間模式切換到內(nèi)核空間模式(一次上下文切換)。內(nèi)核空間通過 DAM 將數(shù)據(jù)讀取到內(nèi)核空間的一個緩沖區(qū)(第一次拷貝)完成真正向磁盤讀取數(shù)據(jù)的請求,。將內(nèi)核空間緩沖區(qū)中的數(shù)據(jù)通過 CPU 再次拷貝到用戶空間的緩沖區(qū)中(第二次拷貝)。讀取操作完成,程序?qū)?shù)據(jù)業(yè)務(wù)處理。
        • 寫虛擬機從用戶空間向內(nèi)核空間發(fā)起一個寫命令的系統(tǒng)調(diào)用,將用戶空間緩沖區(qū)中的數(shù)據(jù)通過 CPU 拷貝到內(nèi)核空間緩沖區(qū)。并由用戶空間模式切換到內(nèi)核空間模式(一次上下文切換和一次數(shù)據(jù)拷貝)。內(nèi)核空間通過DAM完成數(shù)據(jù)寫入網(wǎng)絡(luò)的功能(第二次拷貝,將數(shù)據(jù)拷貝到協(xié)議棧)。并返回內(nèi)核空間。內(nèi)核空間將結(jié)果反饋給用戶空間(第二次上下文切換)。結(jié)束。

        代碼示例

        File file = new File(“test.txt”);RandomAccessFile = raf = new RandomAccessFile(file,”rw”);byte[] arr = new byte[(int)file.length()];raf.read(arr);Socket socket = new ServerSocket(8888).accept();socket.getOutputStream().write(arr);

        這段代碼就是讀取一個本地文件內(nèi)容,然后再將其內(nèi)容通過 Socket 連接寫出去。看起來就幾行代碼,但是涉及到多次內(nèi)存拷貝。其流程如下:

      17. 讀取需要文件內(nèi)容將文件內(nèi)容通過 DMA(Direct memory access)(直接內(nèi)存訪問) 拷貝到系統(tǒng)內(nèi)核的 buffer 中;在內(nèi)核 buffer 中通過 CPU 拷貝到用戶 buffer 中;
      18. 寫輸入將數(shù)據(jù)從用戶 buffer 通過 CPU 拷貝到 socket buffer 中;socket buffer 通過 DMA(Direct memory access)(直接內(nèi)存訪問)拷貝到協(xié)議棧;
      19. Java NIO 操作

        Java NIO 中的 transferTo 可以實現(xiàn)零拷貝。零拷貝,并不是不拷貝,而是整個過程不需要進(jìn)行 CPU 拷貝。

        NIO

        首先還是通過 DAM 拷貝到內(nèi)核空間的 buffer 中,然后再通過 CPU 拷貝到 socket buffer ,最后由 DAM 拷貝到協(xié)議棧。但這次的 CPU 拷貝內(nèi)容很少,只拷貝內(nèi)核 buffer 的長度、偏移量等信息,消耗很低,可以忽略。因此稱為零拷貝。減少了用戶空間與內(nèi)核空間的相互切換和數(shù)據(jù)拷貝次數(shù)。

        代碼示例

        傳統(tǒng) I/O 拷貝大文件

        /** * 服務(wù)端 */public class OldIoServer { @SuppressWarnings(“resource”) public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(6666); while (true) { Socket socket = serverSocket.accept(); DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); byte[] byteArray = new byte[4096]; while (true) { int readCount = dataInputStream.read(byteArray, 0, byteArray.length); if (-1 == readCount) { break; } } } }}/** * 客戶端 */public class OldIoClient { @SuppressWarnings(“resource”) public static void main(String[] args) throws Exception { Socket socket = new Socket(“127.0.0.1”, 6666); // 需要拷貝的文件 String fileName = “E:downloadsoftwindowsjdk-8u171-windows-x64.exe”; InputStream inputStream = new FileInputStream(fileName); DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); byte[] buffer = new byte[4096]; long readCount; long total = 0; long start = System.currentTimeMillis(); while ((readCount = inputStream.read(buffer)) >= 0) { total += readCount; dataOutputStream.write(buffer); } long end = System.currentTimeMillis(); System.out.println(“傳輸總字節(jié)數(shù):” + total + “,耗時:” + (end – start) + “毫秒”); dataOutputStream.close(); inputStream.close(); socket.close(); }}

        這里拷貝了一個 JDK ,最后運行結(jié)果如下:

        傳輸總字節(jié)數(shù):217342912,耗時:4803毫秒

        使用 Java NIO 的 transferTo 拷貝

        /** * 服務(wù)端 */public class NioServer { public static void main(String[] args) throws IOException { InetSocketAddress address = new InetSocketAddress(6666); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); ServerSocket serverSocket = serverSocketChannel.socket(); serverSocket.bind(address); ByteBuffer buffer = ByteBuffer.allocate(4096); while (true) { SocketChannel socketChannel = serverSocketChannel.accept(); int readCount = 0; while (-1 != readCount) { readCount = socketChannel.read(buffer); buffer.rewind(); // 倒帶,將position設(shè)置為0,mark設(shè)置為-1 } } }}/** * 客戶端 */public class NioClient { @SuppressWarnings(“resource”) public static void main(String[] args) throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress(“127.0.0.1”, 6666)); String fileName = “E:downloadsoftwindowsjdk-8u171-windows-x64.exe”; FileChannel channel = new FileInputStream(fileName).getChannel(); long start = System.currentTimeMillis(); // 在linux下,transferTo方法可以一次性發(fā)送數(shù)據(jù) // 在windows中,transferTo方法傳輸?shù)奈募^8M得分段 long totalSize = channel.size(); long transferTotal = 0; long position = 0; long count = 8 * 1024 * 1024; if (totalSize > count) { BigDecimal totalCount = new BigDecimal(totalSize).pide(new BigDecimal(count)).setScale(0, RoundingMode.UP); for (int i=1; i<=totalCount.intValue(); i++) { if (i == totalCount.intValue()) { transferTotal += channel.transferTo(position, totalSize, socketChannel); } else { transferTotal += channel.transferTo(position, count + position, socketChannel); position = position + count; } } } else { transferTotal += channel.transferTo(position, totalSize, socketChannel); } long end = System.currentTimeMillis(); System.out.println("發(fā)送的總字節(jié):" + transferTotal + ",耗時:" + (end – start) + "毫秒"); channel.close(); socketChannel.close(); }}

        運行結(jié)果如下:

        發(fā)送的總字節(jié):217342912,耗時:415毫秒

        從結(jié)果可以看到,BIO與NIO耗時相差一個數(shù)量級,NIO只要0.4s,而BIO要4s。所以在網(wǎng)絡(luò)傳輸中,使用 NIO 的零拷貝,可以大大提高性能。

        Selector

        Selector 與 I/O 多路復(fù)用

        Selector 稱為選擇器,可以將通道注冊進(jìn)選擇器中,選擇器與通道之間屬于一對多的關(guān)系,也就是使用 1 個線程來操作多個通道。主要作用就是使用 1 個線程來對多個通道中已就緒的通道進(jìn)行選擇,然后就可以對選擇的通道進(jìn)行數(shù)據(jù)處理。這種機制在 NIO 技術(shù)中稱為 I/O 多路復(fù)用。它的優(yōu)勢是可以節(jié)省 CPU 資源,因為只有 1 個線程,CPU 不需要在不同的線程間進(jìn)行上下文切換(線程的上下文切換是一個非常耗時的動作),并且因為線程對象的數(shù)量大幅減少,降低了內(nèi)存占用率,這對設(shè)計高性能服務(wù)器具有很重要的意義。

        多路復(fù)用的核心目的是使用最少的線程去操作更多的通道,在其內(nèi)部其實并不永遠(yuǎn)只是一個線程,線程數(shù)量會隨著通道的多少而動態(tài)地增減以進(jìn)行適配。在 JDK 源碼中,創(chuàng)建線程的個數(shù)是根據(jù)通道的數(shù)量來決定的,每注冊 1023 個通道就創(chuàng)建 1 個新的線程。

        注意:

        在使用 I/O 多路復(fù)用時,這個線程不是以 for 循環(huán)的方式來判斷每個通道是否有數(shù)據(jù)進(jìn)行處理,而是以操作系統(tǒng)底層作為”通知器”,來 “通知JVM 中的線程”哪個通道中的數(shù)據(jù)需要進(jìn)行處理。當(dāng)不使用 for 循環(huán)的方式來進(jìn)行判斷,而是使用通知的方式時,這就大大提高了程序運行的效率,不會出現(xiàn)無限期的 for 循環(huán)迭代空運行了。

        Selector 類是抽象類,是 SelectableChannel 對象的多路復(fù)用器。也就是說只有 selectableChannel 通道才能被 Selector 所復(fù)用。

        通過 Selector.open() 方法來獲得一個 Selector 對象。open() 方法內(nèi)部又是通過 SelectorProvider 對象來獲取/打開一個選擇器并返回的。SelectorProvider 類的作用是用于選擇器和可選擇通道的服務(wù)提供者類,給定的對 Java 虛擬機的調(diào)用維護了單個系統(tǒng)級的默認(rèn)提供者實例,它由 provider() 方法返回。

        在通過調(diào)用選擇器的close() 方法關(guān)閉選擇器之前,選擇器一直保持打開狀態(tài)。

        通過 SelectionKey 對象來表示 SelectableChannel 到選擇器的注冊。選擇器維護了 3 種 SelectionKey-Set (選擇鍵集),在新建的選擇器中,這 3 個集合都是空集合:

      20. 鍵集:包含的鍵表示當(dāng)前通道到此選擇器的注冊,也就是通過某個通道的 register() 方法注冊該通道時,所帶來的的影響是向選擇器的鍵集中添加了一個鍵。此集合由 keys() 方法返回。鍵集本身是不可直接修改的。
      21. 已選擇鍵集:在首先調(diào)用 select() 方法選擇期間,檢測每個鍵的通道是否已經(jīng)至少為該鍵相關(guān)操作集所標(biāo)識的一個操作準(zhǔn)備就緒,然后調(diào)用 selectedKeys() 方法返回已就緒鍵的集合。已選擇鍵集始終是鍵集的一個子集。
      22. 已取消鍵集:標(biāo)識已被取消但其通道尚未注銷的鍵的集合。不可直接訪問此集合。已取消鍵集始終是鍵集的一個子集。在 select() 方法選擇操作期間,從鍵集中移除已取消的鍵。
      23. 無論是通過關(guān)閉某個鍵的通道還是調(diào)用該鍵的 cancel() 方法來取消鍵,該鍵都被添加到選擇器的已取消鍵集中。取消某個鍵會導(dǎo)致在下一次 select() 方法選擇操作期間注銷該鍵的通道,而在注銷時將從所有選擇器的鍵集中移除該鍵。

        通過 select() 方法選擇操作將鍵添加到已選擇鍵集中??赏ㄟ^調(diào)用已選擇鍵集的 remove() 方法,或者通過調(diào)用從該鍵集獲得的 iterator 的 remove() 方法直接移除某個鍵。通過任何其他方式都無法直接將鍵從已選擇鍵集中移除。無法將鍵直接添加到已選擇鍵集中。

        select() 方法返回值的含義是已更新其準(zhǔn)備就緒操作集的鍵的數(shù)目,該數(shù)目可能為零或非零,非零的情況就是新的準(zhǔn)備就緒鍵的個數(shù)。零說明當(dāng)前沒有通道準(zhǔn)備就緒,更新準(zhǔn)備就緒操作集的鍵的個數(shù)為零,如果沒有調(diào)用 remove() 方法,此時準(zhǔn)備就緒操作集的鍵與上次保持一致。

        注意:

        在每次處理完一個已選擇鍵對應(yīng)的事件后,需要手動調(diào)用 remove() 方法將其從已選擇鍵集中移除,不然會造成重復(fù)消費的情況,導(dǎo)致程序異常。

        在每次 select() 操作期間,都可以將鍵添加到選擇器的已選擇鍵集或從中將其移除,并且可以從其鍵集合已取消鍵集中移除。涉及以下 3 個步驟:

      24. 將已取消鍵集中的每個鍵從所有鍵集中移除,并注銷其通道。此步驟使已取消鍵集成為空集。
      25. 在開始進(jìn)行 select() 方法選擇操作時,應(yīng)查詢基礎(chǔ)操作系統(tǒng)來更新每個剩余通道的準(zhǔn)備就緒信息,以執(zhí)行由其鍵的相關(guān)集合所標(biāo)識的任意操作。如果該通道的鍵尚未在已選擇鍵集中,則將其添加到該集合中,并修改其準(zhǔn)備就緒操作集,以準(zhǔn)確的標(biāo)識那些通道現(xiàn)在已報告為之準(zhǔn)備就緒的操作。丟棄準(zhǔn)備就緒操作集中以前記錄的所有準(zhǔn)備就緒信息。如果該通道已經(jīng)在已選擇鍵集中,則修改其準(zhǔn)備就緒操作集,以準(zhǔn)確的標(biāo)識所有通道已報告為之準(zhǔn)備就緒的新操作。保留準(zhǔn)備就緒操作以前記錄記錄的所有準(zhǔn)備就緒信息。換句話說,基礎(chǔ)系統(tǒng)所返回的準(zhǔn)備就緒操作集是和該鍵的當(dāng)前準(zhǔn)備就緒操作集按位分開的。如果在此步驟開始時鍵集中的所有鍵都為空的相關(guān)集合,則不會更新已選擇鍵集合任意鍵的準(zhǔn)備就緒操作集。
      26. 如果在步驟 2 進(jìn)行時已將任何鍵添加到已取消的鍵集,則他們按照步驟 1 進(jìn)行處理。
      27. 在執(zhí)行選擇操作的過程中,更改選擇器鍵的相關(guān)集合對該操作沒有影響,在進(jìn)行下一次選擇操作時才會看到此更改。一般情況下,選擇器的鍵和已選擇鍵集由多個并發(fā)線程使用是不安全的。

        返回值類型

        方法名

        作用

        int

        select()

        同步操作,選擇一組鍵,其相應(yīng)的通道已為 I/O 操作準(zhǔn)備就緒

        int

        select(long timeout)

        在指定時間內(nèi)同步操作,選擇一組鍵,其相應(yīng)的通道已為 I/O 操作準(zhǔn)備就緒。如果 timeout 參數(shù)為 0 則無限期地阻塞

        int

        selectNow()

        非阻塞操作,選擇一組鍵,其相應(yīng)的通道已為 I/O 操作準(zhǔn)備就緒

        SelectionKey

        SelectionKey 表示 SelectableChannel 在選擇器中的注冊的標(biāo)記。

        SelectionKey 支持將單個任意對象附加到某個鍵的操作??赏ㄟ^ attach() 方法附加對象,然后通過 attachment() 方法獲取該對象。

        返回值類型

        方法名

        作用

        SelectableChannel

        cancel()

        將 SelectionKey 放入取消鍵集中,并且在下一次執(zhí)行 select() 方法是刪除這個 SelectionKey 所有的鍵集,并且注銷其對應(yīng)的通道。

        boolean

        isAcceptable()

        測試此鍵的通道是否已準(zhǔn)備好接受新的套接字連接。

        boolean

        isConnectable()

        測試此鍵的通道是否已完成其套接字的連接操作。

        boolean

        isReadable()

        測試此鍵的通道是否已準(zhǔn)備好進(jìn)行讀取。

        boolean

        isWritable()

        測試此鍵的通道是否已準(zhǔn)備好進(jìn)行寫入。

        Selector

        selector()

        返回 SelectionKey 關(guān)聯(lián)的選擇器,即使已取消該鍵,此方法仍然有效。

        Object

        attach(Object obj)

        將給定的對象附加到此鍵。一次只能附加一個對象,調(diào)用此方法會導(dǎo)致丟棄所有以前的附加對象。返回值代表先前已附加的對象(如果有),否則返回 null。

        Object

        attachment()

        獲取已附加的對象(如果有),否則返回 null。

        SelectableChannel

        SelectableChannel 類和 FileChannel 類是平級關(guān)系,都是繼承自父類 AbstractInterruptibleChannel。抽象類 SelectableChannel 有很多子類,這里只展示了在 Socket 編程中常用的 ServerSocketChannel 和 SocketChannel, 如下圖:

        SelectableChannel 類可以通過選擇器實現(xiàn)多路復(fù)用。在與選擇器結(jié)合使用的時候,首先需要調(diào)用 SelectableChannel 對象的 register() 方法在選擇器對象里注冊當(dāng)前 SelectableChannel。

        一個通道最多只能在任意特定選擇器上注冊一次??梢酝ㄟ^ isRegistered() 方法來確定是否已經(jīng)向一個或多個選擇器注冊了某個通道。

        新創(chuàng)建的 SelectableChannel 總是處于阻塞模式,在結(jié)合使用基于選擇器的多路復(fù)用時,向選擇器注冊某個通道前,必須先將該通道置于非阻塞模式。

        ServerSocketChannel 類是抽象的,可通過其 open() 方法創(chuàng)建實例。其實 ServerSocketChannel 和 SocketChannel 只是對 ServerSocket 和 Socket 的封裝,目的就是要結(jié)合選擇器達(dá)到多路復(fù)用的效果。單純的使用 SocketChannel 只是對 ServerSocket 是實現(xiàn)不了 I/O 多路復(fù)用的。

        SelectionKey register(Selector sel, int ops) 方法的作用是向給定的選擇器注冊此通道,返回一個選擇鍵(SelectionKey)。參數(shù) sel 代表要向其注冊此通道的選擇器,ops 參數(shù)就是通道感興趣的時間,也就是通道能執(zhí)行操作的集合,包括:SelectionKey.OP_ACCEPT – 用于套接字接受操作的操作集位、SelectionKey.OP_CONNECT – 用于套接字連接操作的操作集位、SelectionKey.OP_READ – 用于讀取操作的操作集位和 SelectionKey.OP_WRITE – 用于寫入操作的操作集位。

        鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
        上一篇 2022年8月2日 09:09
        下一篇 2022年8月2日 09:09

        相關(guān)推薦

        聯(lián)系我們

        聯(lián)系郵箱:admin#wlmqw.com
        工作時間:周一至周五,10:30-18:30,節(jié)假日休息