成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

JAVA_NIO詳細(xì)解析說(shuō)明

SillyMonkey / 1129人閱讀

摘要:通道是和選擇器一起被注冊(cè)的,并且使用選擇器來(lái)更新通道的就緒狀態(tài)。注冊(cè)不會(huì)立即被取消,但鍵會(huì)立即失效。這個(gè)集合的每個(gè)成員都是相關(guān)的通道被選擇器在前一個(gè)選擇操作中判斷為已經(jīng)準(zhǔn)備好的,并且包

Java NIO是一個(gè)用來(lái)替代標(biāo)準(zhǔn)Java IO API的新型數(shù)據(jù)傳遞方式,像現(xiàn)在分布式架構(gòu)中會(huì)經(jīng)常存在他的身影。其比傳統(tǒng)的IO更加高效,非阻塞,異步,雙向

NIO主體結(jié)構(gòu)

Java NIO的主要構(gòu)成核心就是Buffer、Channel和Selector這三個(gè)

對(duì)于Channel我想要提醒的是,Channel中的數(shù)據(jù)總是要先讀到一個(gè)Buffer,或者總是要從一個(gè)Buffer中寫入

使用Selector,得向Selector注冊(cè)Channel,然后調(diào)用它的select()方法。這個(gè)方法會(huì)一直阻塞到某個(gè)注冊(cè)的通道有事件就緒。一旦這個(gè)方法返回,線程就可以處理這些事件

Channel

所有的 IO 在NIO 中都從一個(gè)Channel 開(kāi)始。Channel 有點(diǎn)象流

Channel的實(shí)現(xiàn)

FileChannel:從文件中讀寫數(shù)據(jù)

DatagramChannel:通過(guò)UDP讀寫網(wǎng)絡(luò)中的數(shù)據(jù)

SocketChannel:通過(guò)TCP讀寫網(wǎng)絡(luò)中的數(shù)據(jù)

ServerSocketChannel:監(jiān)聽(tīng)新進(jìn)來(lái)的TCP連接,像Web服務(wù)器那樣。對(duì)每一個(gè)新進(jìn)來(lái)的連接都會(huì)創(chuàng)建一個(gè)SocketChannel

Scatter/Gather

分散(scatter)從Channel中讀取是指在讀操作時(shí)將讀取的數(shù)據(jù)寫入多個(gè)buffer中。因此,Channel將從Channel中讀取的數(shù)據(jù)“分散(scatter)”到多個(gè)Buffer中

聚集(gather)寫入Channel是指在寫操作時(shí)將多個(gè)buffer的數(shù)據(jù)寫入同一個(gè)Channel,因此,Channel 將多個(gè)Buffer中的數(shù)據(jù)“聚集(gather)”后發(fā)送到Channel

通過(guò)這樣的方式可以方便數(shù)據(jù)的讀取,當(dāng)你想要獲取整個(gè)數(shù)據(jù)的一部分的時(shí)候,通過(guò)這種方式可以很快的獲取數(shù)據(jù)

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);

read()方法按照buffer在數(shù)組中的順序?qū)腸hannel中讀取的數(shù)據(jù)寫入到buffer,當(dāng)一個(gè)buffer被寫滿后,channel緊接著向另一個(gè)buffer中寫

transferFrom、transferTo

實(shí)現(xiàn)兩個(gè)Channel之間相互連接,數(shù)據(jù)傳遞

    public static void trainforNio() {
        RandomAccessFile fromFile=null;
        RandomAccessFile toFile=null;
        try {

            fromFile = new RandomAccessFile("src/nio.txt", "rw");
            // channel獲取數(shù)據(jù)
            FileChannel fromChannel = fromFile.getChannel();
            toFile = new RandomAccessFile("src/toFile.txt", "rw");
            FileChannel toChannel = toFile.getChannel();
            System.out.println(toChannel.size());
              //position處開(kāi)始向目標(biāo)文件寫入數(shù)據(jù),這里是toChannel
            long position = toChannel.size();
            long count = fromChannel.size();
            toChannel.transferFrom(fromChannel, position, count);
            System.out.println(toChannel.size());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fromFile != null) {
                    fromFile.close();
                }
                if (toFile != null) {
                    toFile.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

transferFrom、transferTo作用是一樣的,只是一個(gè)是tochannal調(diào)用,一個(gè)是fromchannnal調(diào)用

在實(shí)際的運(yùn)用中可能存在源通道的剩余空間小于 count 個(gè)字節(jié),則所傳輸?shù)淖止?jié)數(shù)要小于請(qǐng)求的字節(jié)數(shù)

在SoketChannel的實(shí)現(xiàn)中,SocketChannel只會(huì)傳輸此刻準(zhǔn)備好的數(shù)據(jù)(可能不足count字節(jié))。因此,SocketChannel可能不會(huì)將請(qǐng)求的所有數(shù)據(jù)(count個(gè)字節(jié))全部傳輸?shù)紽ileChannel中

看官一定要仔細(xì)看我栗子中的注釋

Buffer

Buffer是一個(gè)緩存區(qū),其會(huì)將Channel中的數(shù)據(jù)存儲(chǔ)起來(lái)

Buffer的實(shí)現(xiàn)

ByteBuffer

CharBuffer

DoubleBuffer

FloatBuffer

IntBuffer

LongBuffer

ShortBuffer

MappedByteBuffer

capacity,position,limit

在講解該主題之前,首先要明白讀模式和寫模式,無(wú)論是Channel還是Buffer都存在這兩種模式,要理解這兩種模式,第一步要明確主題是哪一個(gè),是Channel還是Buffer。舉個(gè)栗子,主角是Channel,讀模式的含義就是從Buffer中獲取數(shù)據(jù),寫模式就是將數(shù)據(jù)寫入Buffer,對(duì)于Buffer則是相反。搞清楚這一點(diǎn),理解下面的就要相對(duì)清楚一點(diǎn)

capacity:作為一個(gè)內(nèi)存塊,其就代表了當(dāng)前Buffer能最多暫存多少數(shù)據(jù)量,存儲(chǔ)的數(shù)據(jù)類型則是根據(jù)上面的Buffer對(duì)象類型,一旦Buffer滿了,需要將其清空(通過(guò)讀數(shù)據(jù)或者清除數(shù)據(jù))才能繼續(xù)寫數(shù)據(jù)往里寫數(shù)據(jù)

position:代表當(dāng)前數(shù)據(jù)讀或?qū)懱幱谀莻€(gè)位置。讀模式:被重置從0開(kāi)始,最大值可能為capacity-1或者limit-1,寫模式:被重置從0開(kāi)始,最大值為limit-1

limit:最多能往Buffer里寫多少數(shù)據(jù),limit大小跟數(shù)據(jù)量大小和capacity有關(guān),讀模式:數(shù)據(jù)量>capacity時(shí),limit=capacity,數(shù)據(jù)量=capacity時(shí),limit=capacity,數(shù)據(jù)量

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class Method {
    public static void nio() {
        RandomAccessFile aFile = null;
        try {

            aFile = new RandomAccessFile("src/nio.txt", "rw");
            // channel獲取數(shù)據(jù)
            FileChannel fileChannel = aFile.getChannel();
            // 初始化Buffer,設(shè)定Buffer每次可以存儲(chǔ)數(shù)據(jù)量
            // 創(chuàng)建的Buffer是1024byte的,如果實(shí)際數(shù)據(jù)本身就小于1024,那么limit就是實(shí)際數(shù)據(jù)大小
            ByteBuffer buf = ByteBuffer.allocate(1024);
            // channel中的數(shù)據(jù)寫入Buffer
            int bytesRead = fileChannel.read(buf);
            System.out.println(bytesRead);

            while (bytesRead != -1) {
                // Buffer切換為讀取模式
                buf.flip();
                // 讀取數(shù)據(jù)
                while (buf.hasRemaining()) {
                    System.out.print((char) buf.get());
                }
                // 清空Buffer區(qū)
                buf.compact();
                // 繼續(xù)將數(shù)據(jù)寫入緩存區(qū)
                bytesRead = fileChannel.read(buf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (aFile != null) {
                    aFile.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Method.nio();
    
Buffer讀寫數(shù)據(jù)步驟

寫入數(shù)據(jù)到Buffer(fileChannel.read(buf))

調(diào)用flip()方法(buf.flip())

從Buffer中讀取數(shù)據(jù)(buf.get())

調(diào)用clear()方法或者compact()方法(buf.compact())

Buffer方法

flip():將Buffer讀模式切換到寫模式,并且將position制為0

clear():清空整個(gè)緩沖區(qū)

compact():只會(huì)清除已經(jīng)讀過(guò)的數(shù)據(jù)。任何未讀的數(shù)據(jù)都被移到緩沖區(qū)的起始處,新寫入的數(shù)據(jù)將放到緩沖區(qū)未讀數(shù)據(jù)的后面

allocate(1024):初始化Buffer,設(shè)定的值就決定capacity值的大小

rewind():將position設(shè)回0,所以你可以重讀Buffer中的所有數(shù)據(jù)。limit保持不變,仍然表示能從Buffer中讀取多少個(gè)元素(byte、char等)

mark()與reset():通過(guò)調(diào)用Buffer.mark()方法,可以標(biāo)記Buffer中的一個(gè)特定position。之后可以通過(guò)調(diào)用Buffer.reset()方法恢復(fù)到這個(gè)position

equals():當(dāng)滿足下面三個(gè)條件時(shí),兩個(gè)Buffer才是相等

有相同的類型(byte、char、int等)

Buffer中剩余的byte、char等的個(gè)數(shù)相等

Buffer中所有剩余的byte、char等都相同

只比較的是剩余的數(shù)據(jù)

compareTo():滿足下列條件,則認(rèn)為一個(gè)Buffer“小于”另一個(gè)Buffer

第一個(gè)不相等的元素小于另一個(gè)Buffer中對(duì)應(yīng)的元素

所有元素都相等,但第一個(gè)Buffer比另一個(gè)先耗盡(第一個(gè)Buffer的元素個(gè)數(shù)比另一個(gè)少)

Selector

Selector允許單線程處理多個(gè) Channel。如果你的應(yīng)用打開(kāi)了多個(gè)連接(通道),但每個(gè)連接的流量都很低,使用Selector就會(huì)很方便

大致流程

當(dāng)您調(diào)用一個(gè)選擇器對(duì)象的 select( )方法時(shí),相關(guān)的鍵會(huì)被更新,用來(lái)檢查所有被注冊(cè)到該選擇器的通道。您可以獲取一個(gè)鍵的集合,從而找到當(dāng)時(shí)已經(jīng)就緒的通道。通過(guò)遍歷這些鍵,您可以選擇出每個(gè)從上次您調(diào)用 select( )開(kāi)始直到現(xiàn)在,已經(jīng)就緒的通道

選擇器(Selector)的特點(diǎn)
public abstract class Selector
{
// This is a partial API listing
public static Selector open( ) throws IOException
public abstract boolean isOpen( );//判斷是open
public abstract void close( ) throws IOException;//選擇鍵設(shè)置無(wú)效
public abstract SelectionProvider provider( );
}

選擇器類管理著一個(gè)被注冊(cè)的通道集合的信息和它們的就緒狀態(tài)。通道是和選擇器一起被注冊(cè)
的,并且使用選擇器來(lái)更新通道的就緒狀態(tài)。當(dāng)這么做的時(shí)候,可以選擇將被激發(fā)的線程掛起,直

到有就緒的的通道

不能注冊(cè)已經(jīng)關(guān)閉的selectableChannel

通過(guò)調(diào)用一個(gè)自定義的 SelectorProvider對(duì)象的 openSelector( )方法來(lái)創(chuàng)建一個(gè) Selector 實(shí)例也是可行的。您可以通過(guò)調(diào)用 provider( )方法來(lái)決定由哪個(gè) SelectorProvider 對(duì)象來(lái)創(chuàng)建給定的 Selector 實(shí)例

通道(Channel)的特點(diǎn)
public abstract class SelectableChannel
extends AbstractChannel
implements Channel
{
// This is a partial API listing
public abstract SelectionKey register (Selector sel, int ops)
throws ClosedChannelException;
public abstract SelectionKey register (Selector sel, int ops,
Object att)
throws ClosedChannelException;
public abstract boolean isRegistered( );
public abstract SelectionKey keyFor (Selector sel);
public abstract int validOps( );
}

繼承SelectableChannel

一個(gè)channel可以注冊(cè)到多個(gè)selector中

一個(gè)selector中同一個(gè)channel只能有一個(gè)

通道被注冊(cè)前,要非阻塞模式

支持Connect、Accept、Read、Write四種可選擇操作事件,但并不是所有的SelectableChannel都存在以上四類,可以通過(guò)validOps()獲取可以使用的操作事件集合

如果你對(duì)不止一種事件感興趣,那么可以用“位或”操作符將常量連接起來(lái)

任何一個(gè)通道和選擇器的注冊(cè)關(guān)系都被封裝在一個(gè) SelectionKey 對(duì)象中。 keyFor( )方法將
返回與該通道和指定的選擇器相關(guān)的鍵。如果通道被注冊(cè)到指定的選擇器上,那么相關(guān)的鍵將被返

回。如果它們之間沒(méi)有注冊(cè)關(guān)系,那么將返回 null

選擇鍵(SelectionKey)的特點(diǎn)
package java.nio.channels;
public abstract class SelectionKey
{
public static final int OP_READ
public static final int OP_WRITE
public static final int OP_CONNECT
public static final int OP_ACCEPT
public abstract SelectableChannel channel( );
public abstract Selector selector( );
public abstract void cancel( );
public abstract boolean isValid( );
public abstract int interestOps( );
public abstract void interestOps (int ops);
public abstract int readyOps( );
public final boolean isReadable( )
public final boolean isWritable( )
public final boolean isConnectable( )
public final boolean isAcceptable( )
public final Object attach (Object ob)
public final Object attachment( )
}

封裝了特定的通道與特定的選擇器的注冊(cè)關(guān)系

一個(gè) SelectionKey 對(duì)象包含兩個(gè)以整數(shù)形式進(jìn)行編碼的byte掩碼:一個(gè)用于指示那些通道/
選擇器組合體所關(guān)心的操作(instrest 集合),另一個(gè)表示通道準(zhǔn)備好要執(zhí)行的操作( ready 集合)

當(dāng)終結(jié)注冊(cè)關(guān)系時(shí)

當(dāng)應(yīng)該終結(jié)這種關(guān)系的時(shí)候,可以調(diào)用 SelectionKey對(duì)象的 cancel( )方法??梢酝ㄟ^(guò)調(diào)用 isValid( )方法來(lái)檢查它是否仍然表示一種有效的關(guān)系。當(dāng)鍵被取消時(shí),它將被放在相關(guān)的選擇器的已取消的鍵的集合里。注冊(cè)不會(huì)立即被取消,但鍵會(huì)立即失效。當(dāng)再次調(diào)用 select( )方法時(shí)(或者一個(gè)正在進(jìn)行的 select()調(diào)用結(jié)束時(shí)),已取消的鍵的集合中的被取消的鍵將被清理掉,并且相應(yīng)的注銷也將完成。通道會(huì)被注銷,而新的SelectionKey 將被返回

當(dāng)通道關(guān)閉時(shí)

當(dāng)通道關(guān)閉時(shí),所有相關(guān)的鍵會(huì)自動(dòng)取消(記住,一個(gè)通道可以被注冊(cè)到多個(gè)選擇器上)。當(dāng)
選擇器關(guān)閉時(shí),所有被注冊(cè)到該選擇器的通道都將被注銷,并且相關(guān)的鍵將立即被無(wú)效化(取
消)。一旦鍵被無(wú)效化,調(diào)用它的與選擇相關(guān)的方法就將拋出 CancelledKeyException

interest 集合

當(dāng)前的 interest 集合可以通過(guò)調(diào)用鍵對(duì)象的 interestOps( )方法來(lái)獲取

最初,這應(yīng)該是通道被注冊(cè)時(shí)傳進(jìn)來(lái)的值。這個(gè) interset 集合永遠(yuǎn)不會(huì)被選擇器改變,但您可以通過(guò)調(diào)用 interestOps( )方法并傳入一個(gè)新的byte掩碼參數(shù)來(lái)改變它。 interest 集合也可以通過(guò)將通道注冊(cè)到選擇器上來(lái)改變(實(shí)際上使用一種迂回的方式調(diào)用 interestOps( )),就像 4.1.2 小節(jié)中描的那樣。當(dāng)相關(guān)的 Selector 上的 select( )操作正在進(jìn)行時(shí)改變鍵的 interest 集合,不會(huì)影響那個(gè)正在進(jìn)行的選擇操作。所有更改將會(huì)在 select( )的下一個(gè)調(diào)用中體現(xiàn)出來(lái)

ready集合

可以通過(guò)調(diào)用鍵的 readyOps( )方法來(lái)獲取相關(guān)的通道的已經(jīng)就緒的操作。 ready 集合是 interest
集合的子集,并且表示了 interest 集合中從上次調(diào)用 select( )以來(lái)已經(jīng)就緒的那些操作

SelectionKey 類定義了四個(gè)便于使用的布爾方法來(lái)為您測(cè)試這些byte值: isReadable( ), isWritable( ), isConnectable( ), 和 isAcceptable( )

SelectionKey 對(duì)象包含的 ready 集合與最近一次選擇器對(duì)所注冊(cè)的通道所作的檢查相同。而每個(gè)多帶帶的通道的就緒狀態(tài)會(huì)同時(shí)改變

附加的對(duì)象

可以將一個(gè)對(duì)象或者更多信息附著到SelectionKey上,這樣就能方便的識(shí)別某個(gè)給定的通道。例如,可以附加 與通道一起使用的Buffer,或是包含聚集數(shù)據(jù)的某個(gè)對(duì)象。使用方法如下:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

還可以在用register()方法向Selector注冊(cè)Channel的時(shí)候附加對(duì)象。如:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

如果選擇鍵的存續(xù)時(shí)間很長(zhǎng),但您附加的對(duì)象不應(yīng)該存在那么長(zhǎng)時(shí)間,請(qǐng)記得在完成后清理附件。否則,您附加的對(duì)象將不能被垃圾回收,您將會(huì)面臨內(nèi)存泄漏問(wèn)題

總體上說(shuō), SelectionKey 對(duì)象是線程安全的,但知道修改 interest 集合的操作是通過(guò) Selector 對(duì)象進(jìn)行同步的是很重要的。這可能會(huì)導(dǎo)致 interestOps( )方法的調(diào)用會(huì)阻塞不確定長(zhǎng)的一段時(shí)間。選擇器所使用的鎖策略(例如是否在整個(gè)選擇過(guò)程中保持這些鎖)是依賴于具體實(shí)現(xiàn)的。幸好,這種多元處理能力被特別地設(shè)計(jì)為可以使用單線程來(lái)管理多個(gè)通道。被多個(gè)線程使用的選擇器也只會(huì)在系統(tǒng)特別復(fù)雜時(shí)產(chǎn)生問(wèn)題。

選擇過(guò)程
public abstract class Selector
{
public abstract Set keys( );
public abstract Set selectedKeys( );
public abstract int select( ) throws IOException;
public abstract int select (long timeout) throws IOException;
public abstract int selectNow( ) throws IOException;
public abstract void wakeup( );
}

已注冊(cè)的鍵的集合

與選擇器關(guān)聯(lián)的已經(jīng)注冊(cè)的鍵的集合。并不是所有注冊(cè)過(guò)的鍵都仍然有效。這個(gè)集合通過(guò)
keys( )方法返回,并且可能是空的。這個(gè)已注冊(cè)的鍵的集合不是可以直接修改的;試圖這么做的話
將引 java.lang.UnsupportedOperationException。

已選擇的鍵的集合

已注冊(cè)的鍵的集合的子集。這個(gè)集合的每個(gè)成員都是相關(guān)的通道被選擇器(在前一個(gè)選擇操作
中)判斷為已經(jīng)準(zhǔn)備好的,并且包含于鍵的 interest 集合中的操作。這個(gè)集合通過(guò) selectedKeys( )方
法返回(并有可能是空的)

不要將已選擇的鍵的集合與 ready 集合弄混了。這是一個(gè)鍵的集合,每個(gè)鍵都關(guān)聯(lián)一個(gè)已經(jīng)準(zhǔn)
備好至少一種操作的通道。每個(gè)鍵都有一個(gè)內(nèi)嵌的 ready 集合,指示了所關(guān)聯(lián)的通道已經(jīng)準(zhǔn)備好的
操作

鍵可以直接從這個(gè)集合中移除,但不能添加

已取消的鍵的集合

已注冊(cè)的鍵的集合的子集,這個(gè)集合包含了 cancel( )方法被調(diào)用過(guò)的鍵(這個(gè)鍵已經(jīng)被無(wú)效
化),但它們還沒(méi)有被注銷。這個(gè)集合是選擇器對(duì)象的私有成員,因而無(wú)法直接訪問(wèn)

在一個(gè)剛初始化的 Selector 對(duì)象中,這三個(gè)集合都是空的。

執(zhí)行步驟

已取消的鍵的集合將會(huì)被檢查。如果它是非空的,每個(gè)已取消的鍵的集合中的鍵將從另外兩
個(gè)集合中移除,并且相關(guān)的通道將被注銷。這個(gè)步驟結(jié)束后,已取消的鍵的集合將是空的。

已注冊(cè)的鍵的集合中的鍵的 interest 集合將被檢查。在這個(gè)步驟中的檢查執(zhí)行過(guò)后,對(duì)
interest 集合的改動(dòng)不會(huì)影響剩余的檢查過(guò)程。

a.如果通道的鍵還沒(méi)有處于已選擇的鍵的集合中,那么鍵的 ready 集合將被清空,然后表示操
作系統(tǒng)發(fā)現(xiàn)的當(dāng)前通道已經(jīng)準(zhǔn)備好的操作的比特掩碼將被設(shè)置。

b.否則,也就是鍵在已選擇的鍵的集合中。鍵的 ready 集合將被表示操作系統(tǒng)發(fā)現(xiàn)的當(dāng)前已經(jīng)
準(zhǔn)備好的操作的比特掩碼更新。所有之前的已經(jīng)不再是就緒狀態(tài)的操作不會(huì)被清除。事實(shí)上,所有的比特位都不會(huì)被清理。由操作系統(tǒng)決定的 ready 集合是與之前的 ready 集合按位分離的,一旦鍵被放置于選擇器的已選擇的鍵的集合中,它的 ready 集合將是累積的。比特位只會(huì)被設(shè)置,不會(huì)被清理。

步驟 2 可能會(huì)花費(fèi)很長(zhǎng)時(shí)間,特別是所激發(fā)的線程處于休眠狀態(tài)時(shí)。與該選擇器相關(guān)的鍵可
能會(huì)同時(shí)被取消。當(dāng)步驟 2 結(jié)束時(shí),步驟 1 將重新執(zhí)行,以完成任意一個(gè)在選擇進(jìn)行的過(guò)程中,鍵

已經(jīng)被取消的通道的注銷。

select 操作返回的值是 ready 集合在步驟 2 中被修改的鍵的數(shù)量,而不是已選擇的鍵的集合中
的通道的總數(shù)。返回值不是已準(zhǔn)備好的通道的總數(shù),而是從上一個(gè) select( )調(diào)用之后進(jìn)入就緒狀態(tài)

的通道的數(shù)量。之前的調(diào)用中就緒的,并且在本次調(diào)用中仍然就緒的通道不會(huì)被計(jì)入,而那些在前
一次調(diào)用中已經(jīng)就緒但已經(jīng)不再處于就緒狀態(tài)的通道也不會(huì)被計(jì)入。這些通道可能仍然在已選擇的
鍵的集合中,但不會(huì)被計(jì)入返回值中。返回值可能是 0。

為什么延遲注銷

使用內(nèi)部的已取消的鍵的集合來(lái)延遲注銷,是一種防止線程在取消鍵時(shí)阻塞,并防止與正在進(jìn)
行的選擇操作沖突的優(yōu)化。注銷通道是一個(gè)潛在的代價(jià)很高的操作,這可能需要重新分配資源(請(qǐng)
記住,鍵是與通道相關(guān)的,并且可能與它們相關(guān)的通道對(duì)象之間有復(fù)雜的交互)。

三種select()方法

僅僅在它們?cè)谒?cè)的通道當(dāng)前都沒(méi)有就緒時(shí),是否阻塞的方面有所不同。

select():在沒(méi)有通道就緒時(shí)將無(wú)限阻塞。一旦至少有一個(gè)已注冊(cè)的通道就緒,選擇器的選擇鍵
就會(huì)被更新,并且每個(gè)就緒的通道的 ready 集合也將被更新。返回值將會(huì)是已經(jīng)確定就緒的通道的

數(shù)目。正常情況下, 這些方法將返回一個(gè)零的值,因?yàn)橹钡揭粋€(gè)通道就緒前它都會(huì)阻塞。

select(long timeout):如果在您提供的超時(shí)時(shí)間(以毫秒計(jì)算)內(nèi)沒(méi)有通道就緒時(shí),它將返回 0。如果一個(gè)或者多個(gè)通道在時(shí)間限制終止前就緒,鍵的狀態(tài)將會(huì)被更新,并且方法會(huì)在那時(shí)立即返回。將超時(shí)參數(shù)指定為 0 表示將無(wú)限期等待,那么它就在各個(gè)方面都等同于使用select()

selectNow():執(zhí)行就緒檢查過(guò)程,但不阻塞。如果當(dāng)前沒(méi)有通道就緒,它將立即返回 0

停止選擇過(guò)程

wakeUp()

某個(gè)線程調(diào)用select()方法后阻塞了,即使沒(méi)有通道已經(jīng)就緒,也有辦法讓其從select()方法返回。只要讓其它線程在第一個(gè)線程調(diào)用select()方法的那個(gè)對(duì)象上調(diào)用Selector.wakeup()方法即可。阻塞在select()方法上的線程會(huì)立馬返回。

如果有其它線程調(diào)用了wakeup()方法,但當(dāng)前沒(méi)有線程阻塞在select()方法上,下個(gè)調(diào)用select()方法的線程會(huì)立即“醒來(lái)(wake up)”。

close()

用完Selector后調(diào)用其close()方法會(huì)關(guān)閉該Selector,且使注冊(cè)到該Selector上的所有SelectionKey實(shí)例無(wú)效。通道本身并不會(huì)關(guān)閉。

interrupt()

如果睡眠中的線程的 interrupt( )方法被調(diào)用,它的返回狀態(tài)將被設(shè)置。如果被喚醒的線程之后
將試圖在通道上執(zhí)行 I/O 操作,通道將立即關(guān)閉,然后線程將捕捉到一個(gè)異常。

例子

服務(wù)端

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NIOServer {
    // 通道管理器
    private Selector selector;

    public void initServer(int port) throws Exception {
        // 獲得一個(gè)ServerSocket通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 設(shè)置通道為 非阻塞
        serverChannel.configureBlocking(false);
        // 將該通道對(duì)于的serverSocket綁定到port端口
        serverChannel.socket().bind(new InetSocketAddress(port));
        // 獲得一耳光通道管理器
        this.selector = Selector.open();

        // 將通道管理器和該通道綁定,并為該通道注冊(cè)selectionKey.OP_ACCEPT事件
        // 注冊(cè)該事件后,當(dāng)事件到達(dá)的時(shí)候,selector.select()會(huì)返回,
        // 如果事件沒(méi)有到達(dá)selector.select()會(huì)一直阻塞

        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    // 采用輪訓(xùn)的方式監(jiān)聽(tīng)selector上是否有需要處理的事件,如果有,進(jìn)行處理
    public void listen() throws Exception {
        System.out.println("start server");
        // 輪詢?cè)L問(wèn)selector
        while (true) {
            // 當(dāng)注冊(cè)事件到達(dá)時(shí),方法返回,否則該方法會(huì)一直阻塞
            selector.select();
            // 獲得selector中選中的相的迭代器,選中的相為注冊(cè)的事件
            Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 刪除已選的key 以防重負(fù)處理
                ite.remove();
                // 客戶端請(qǐng)求連接事件
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    // 獲得和客戶端連接的通道
                    SocketChannel channel = server.accept();
                    // 設(shè)置成非阻塞
                    channel.configureBlocking(false);
                    // 在這里可以發(fā)送消息給客戶端
                    channel.write(ByteBuffer.wrap(new String("hello client").getBytes()));
                    // 在客戶端 連接成功之后,為了可以接收到客戶端的信息,需要給通道設(shè)置讀的權(quán)限
                    channel.register(this.selector, SelectionKey.OP_READ);
                    // 獲得了可讀的事件

                } else if (key.isReadable()) {
                    read(key);
                }

            }
        }
    }

    // 處理 讀取客戶端發(fā)來(lái)的信息事件
    private void read(SelectionKey key) throws Exception {
        // 服務(wù)器可讀消息,得到事件發(fā)生的socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 穿件讀取的緩沖區(qū)
        ByteBuffer buffer = ByteBuffer.allocate(10);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("server receive from client: " + msg);
        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
        channel.write(outBuffer);
    }

    public static void main(String[] args) throws Throwable {
        NIOServer server = new NIOServer();
        server.initServer(8989);
        server.listen();
    }
}

客戶端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NIOClient {

    // 通道管理器
    private Selector selector;

    /**
     * * // 獲得一個(gè)Socket通道,并對(duì)該通道做一些初始化的工作 * @param ip 連接的服務(wù)器的ip // * @param port
     * 連接的服務(wù)器的端口號(hào) * @throws IOException
     */
    public void initClient(String ip, int port) throws IOException { // 獲得一個(gè)Socket通道
        SocketChannel channel = SocketChannel.open(); // 設(shè)置通道為非阻塞
        channel.configureBlocking(false); // 獲得一個(gè)通道管理器
        this.selector = Selector.open(); // 客戶端連接服務(wù)器,其實(shí)方法執(zhí)行并沒(méi)有實(shí)現(xiàn)連接,需要在listen()方法中調(diào)
        // 用channel.finishConnect();才能完成連接
        channel.connect(new InetSocketAddress(ip, port));
        // 將通道管理器和該通道綁定,并為該通道注冊(cè)SelectionKey.OP_CONNECT事件。
        channel.register(selector, SelectionKey.OP_CONNECT);
    }

    /**
     * * // 采用輪詢的方式監(jiān)聽(tīng)selector上是否有需要處理的事件,如果有,則進(jìn)行處理 * @throws // IOException
     * @throws Exception 
     */
    @SuppressWarnings("unchecked")
    public void listen() throws Exception { // 輪詢?cè)L問(wèn)selector
        while (true) {
            // 選擇一組可以進(jìn)行I/O操作的事件,放在selector中,客戶端的該方法不會(huì)阻塞,
            // 這里和服務(wù)端的方法不一樣,查看api注釋可以知道,當(dāng)至少一個(gè)通道被選中時(shí),
            // selector的wakeup方法被調(diào)用,方法返回,而對(duì)于客戶端來(lái)說(shuō),通道一直是被選中的
            selector.select(); // 獲得selector中選中的項(xiàng)的迭代器
            Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next(); // 刪除已選的key,以防重復(fù)處理
                ite.remove(); // 連接事件發(fā)生
                if (key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key.channel(); // 如果正在連接,則完成連接
                    if (channel.isConnectionPending()) {
                        channel.finishConnect();
                    } // 設(shè)置成非阻塞
                    channel.configureBlocking(false);
                    // 在這里可以給服務(wù)端發(fā)送信息哦
                    channel.write(ByteBuffer.wrap(new String("hello server!").getBytes()));
                    // 在和服務(wù)端連接成功之后,為了可以接收到服務(wù)端的信息,需要給通道設(shè)置讀的權(quán)限。
                    channel.register(this.selector, SelectionKey.OP_READ); // 獲得了可讀的事件
                } else if (key.isReadable()) {
                    read(key);
                }
            }
        }
    }

    private void read(SelectionKey key) throws Exception {
        SocketChannel channel = (SocketChannel) key.channel();
        // 穿件讀取的緩沖區(qū)
        ByteBuffer buffer = ByteBuffer.allocate(10);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("client receive msg from server:" + msg);
        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
        channel.write(outBuffer);

    }

    /**
     * * // 啟動(dòng)客戶端測(cè)試 * @throws IOException
     * @throws Exception 
     */
    public static void main(String[] args) throws Exception {
        NIOClient client = new NIOClient();
        client.initClient("localhost", 8989);
        client.listen();
    }
}
參考資料

Java NIO系列教程

Java NIO學(xué)習(xí)8(Selector)

更多內(nèi)容可以關(guān)注微信公眾號(hào),或者訪問(wèn)AppZone網(wǎng)站

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/64819.html

相關(guān)文章

  • JAVA_NIO系列——Channel和Buffer詳解

    摘要:是一個(gè)用來(lái)替代標(biāo)準(zhǔn)的新型數(shù)據(jù)傳遞方式,像現(xiàn)在分布式架構(gòu)中會(huì)經(jīng)常存在他的身影。這個(gè)方法會(huì)一直阻塞到某個(gè)注冊(cè)的通道有事件就緒。保持不變,仍然表示能從中讀取多少個(gè)元素等與通過(guò)調(diào)用方法,可以標(biāo)記中的一個(gè)特定。 Java NIO是一個(gè)用來(lái)替代標(biāo)準(zhǔn)Java IO API的新型數(shù)據(jù)傳遞方式,像現(xiàn)在分布式架構(gòu)中會(huì)經(jīng)常存在他的身影。其比傳統(tǒng)的IO更加高效,非阻塞,異步,雙向 NIO主體結(jié)構(gòu) showIm...

    leon 評(píng)論0 收藏0
  • TiKV 源碼解析系列文章(一)序

    摘要:而源碼解析系列文章則是會(huì)從源碼層面給大家抽絲剝繭,讓大家知道我們內(nèi)部到底是如何實(shí)現(xiàn)的。我們希望通過(guò)該源碼解析系列,能讓大家對(duì)有一個(gè)更深刻的理解。 作者:唐劉 TiKV 是一個(gè)支持事務(wù)的分布式 Key-Value 數(shù)據(jù)庫(kù),有很多社區(qū)開(kāi)發(fā)者基于 TiKV 來(lái)開(kāi)發(fā)自己的應(yīng)用,譬如 titan、tidis。尤其是在 TiKV 成為 CNCF 的 Sandbox 項(xiàng)目之后,吸引了越來(lái)越多開(kāi)發(fā)者的...

    LeviDing 評(píng)論0 收藏0
  • 【云解析 UDNS】操作指南:添加記錄

    摘要:添加記錄操作步驟進(jìn)入域名解析頁(yè)面。,點(diǎn)擊添加記錄。,填寫詳細(xì)的記錄信息。配置說(shuō)明配置說(shuō)明主機(jī)記錄設(shè)置解析記錄的主機(jī)記錄名稱。標(biāo)準(zhǔn)應(yīng)答為返回設(shè)置的全部記錄值,支持全部記錄類型隨機(jī)應(yīng)答為根據(jù)權(quán)重隨機(jī)返回記錄值,僅支持記錄類型。添加記錄操作步驟1、進(jìn)入域名解析 UDNS頁(yè)面。2,點(diǎn)擊添加記錄。3,填寫詳細(xì)的記錄信息。詳細(xì)配置說(shuō)明見(jiàn)下方。4,點(diǎn)擊確定即可添加成功。配置說(shuō)明配置說(shuō)明主機(jī)記錄設(shè)置解析記錄...

    Tecode 評(píng)論0 收藏0
  • 【項(xiàng)目上線】詳細(xì)步驟04:在一臺(tái)云主機(jī)上部署多個(gè)網(wǎng)站,通過(guò)自定義網(wǎng)站名訪問(wèn)項(xiàng)目地址

    摘要:安裝完成后登陸,注意,如果裝的是,協(xié)議改為。舉例我通過(guò)阿里云注冊(cè)的域名是那么這里我可以輸入,或者其他任何指定特定網(wǎng)站,一臺(tái)主機(jī)可以部署多個(gè)網(wǎng)站。 推薦安裝Xftp,是一個(gè)可視化管理云主機(jī)上文件的軟件,方便初學(xué)者學(xué)習(xí)。 安裝完成后登陸,showImg(https://segmentfault.com/img/bVZEAI?w=496&h=702); 注意,如果裝的是xftp 5,協(xié)議改為...

    Jrain 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<