Java系列6 NIO

參考:
http://www.reibang.com/p/362b365e1bcc
http://www.reibang.com/p/5d2c68b89f1d
http://www.reibang.com/p/389e4571cd2c

一.NIO產(chǎn)生背景

  • 高并發(fā)場(chǎng)景的技術(shù)要求
    1)傳統(tǒng)IO即使使用線程池技術(shù),面臨高并發(fā)時(shí)依然線程不足
    2)傳統(tǒng)阻塞I/O模式帝际,大量線程在等待數(shù)據(jù)時(shí)被掛起,CPU利用率低,系統(tǒng)吞吐量差
    3)線程較長(zhǎng)阻塞時(shí)間在網(wǎng)絡(luò)不穩(wěn)定場(chǎng)景下手素,將降低系統(tǒng)可靠性
  • 傳統(tǒng)IO特性
    1)IO是面向流的祖搓、阻塞的
    2)傳統(tǒng)IO模型中佑力,一個(gè)連接對(duì)應(yīng)一個(gè)線程
    3)傳統(tǒng)IO面向流意味著連接每次從流中讀取一個(gè)或多個(gè)字節(jié)底桂,直至全部讀取完畢植袍,沒有緩存在任何地方,同時(shí)不能前后移動(dòng)流中數(shù)據(jù)
    4)傳統(tǒng)IO的各種流是阻塞的戚啥,意味著當(dāng)線程調(diào)用read或write方法時(shí)將被阻塞奋单,不能執(zhí)行其他操作
  • NIO特性
    1)NIO是面向塊的锉试,非阻塞的
    2)NIO面向塊意味著將把數(shù)據(jù)讀取到一個(gè)稍后處理的緩沖區(qū)中猫十,必要時(shí)可在緩沖區(qū)內(nèi)前后移動(dòng),增加數(shù)據(jù)處理的靈活性
    3)NIO非阻塞模式使得線程從某個(gè)通道讀取數(shù)據(jù)或者向某個(gè)通道寫數(shù)據(jù)的過程中呆盖,遇到數(shù)據(jù)等待時(shí)不會(huì)掛起拖云,可執(zhí)行其他工作
    4)NIO通過將多個(gè)Channel以事件注冊(cè)到一個(gè)Selector實(shí)現(xiàn)由一個(gè)線程處理多個(gè)請(qǐng)求

二.NIO核心實(shí)現(xiàn)

NIO核心API Channel,Buffer应又,Selector宙项。數(shù)據(jù)總是從Chanel讀取到Buffer,或從Buffer寫入Channel

1.通道Channel
  • 可以同時(shí)進(jìn)行讀寫(從緩沖區(qū)讀數(shù)據(jù)株扛,或?qū)憯?shù)據(jù)到緩沖區(qū))
  • 可以異步讀寫數(shù)據(jù)
2.緩沖區(qū)Buffer
  • 本質(zhì)是一個(gè)可以寫入數(shù)據(jù)的內(nèi)存塊尤筐,可以再次讀取
  • 讀寫數(shù)據(jù)一般遵循:
    1)數(shù)據(jù)到Buffer
    2)調(diào)用Buffer.flip()方法,將Buffer從寫模式切換到讀模式
    3)從Buffer讀取數(shù)據(jù)
    4)調(diào)用Buffer.clear() 或 Buffer.compat() 方法洞就,清空Buffer
  • Buffer常用標(biāo)志:
    1)Buffer的大小/容量 - Capacity
    2)Buffer當(dāng)前讀寫位置 - Position
    3)Buffer中信息末尾位置 - Limit
    Buffer讀寫模式下的Capacity盆繁、Position、Limit

3.Selector

  • 多個(gè)Channel以事件的方式注冊(cè)到一個(gè)Selector旬蟋,實(shí)現(xiàn)一個(gè)線程處理多個(gè)請(qǐng)求
    一個(gè)線程處理多個(gè)請(qǐng)求
  • 調(diào)用Selector的select()或selectNow()方法時(shí)只返回有數(shù)據(jù)讀取的SelectableChannel實(shí)例


    返回有數(shù)據(jù)讀取的Channel

三.NIO常用方法

1.Buffer類的flip油昂、clear、compact方法

本質(zhì)是設(shè)置控制Buffer狀態(tài)的position、limit冕碟、capacity三個(gè)變量

  • flip方法
    使Buffer從讀狀態(tài)轉(zhuǎn)為寫狀態(tài):當(dāng)前position設(shè)置為limit拦惋,并將position指向數(shù)據(jù)開始位置
public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
 }
flip方法變讀狀態(tài)為寫狀態(tài)
  • clear方法
    重設(shè)緩沖區(qū)以重新接收字符
public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}
clear方法將緩沖區(qū)position清零
  • compact方法
    與clear方法類似,但只清空已讀取的數(shù)據(jù)安寺,還未讀取的數(shù)據(jù)仍保留在緩沖區(qū)

2.Channel類

  • configureBlocking()register()
channel.configureBlocking(false);  //設(shè)置channel為非阻塞
SelectionKey key =channel.register(selector,SelectionKey.OP_READ);  //注冊(cè)channel

3.Selector類

  • SelectionKey
    注冊(cè)到Selector上的實(shí)例
    1)register()方法注冊(cè)的事件類型有4種
    Connect 某個(gè)Channel成功連接到另一服務(wù)器
    Accept 某個(gè)ServerSocketChannel準(zhǔn)備好接收新連接
    Read 某個(gè)Channel有數(shù)據(jù)可讀
    Write 某個(gè)Channel等待寫數(shù)據(jù)
    2)對(duì)應(yīng)于SelectionKey的常量
    SelectionKey.OP_CONNECT
    SelectionKey.OP_ACCEPT
    SelectionKey.OP_READ
    SelectionKey.OP_WRITE
    3)SelectionKey包含如下屬性
    The interest set 感興趣的事件的集合
    The ready set 已經(jīng)準(zhǔn)備就緒的操作的集合
    The Channel
    The Selector
    An attached object(optional) 將對(duì)象或信息attach到SelectionKey以便識(shí)別
  • select()
    返回int值表示有多少通道已就緒厕妖,包含重載方法
    1)int select():阻塞到至少有一個(gè)通道在注冊(cè)的事件上就緒
    2)int select(long timeout):和select()一樣,但設(shè)定阻塞時(shí)間上限timeout毫秒
    3)int selectNow():不會(huì)阻塞挑庶,不管什么通道就緒都立刻返回叹放。如果自從前一次select后沒有通道就緒,則返回0
  • selectedKeys()
    在調(diào)用select()獲取就緒通道數(shù)后挠羔,可通過selectedKeys()方法返回就緒的Channel井仰,之后可通過迭代SelectionKey獲得就緒的Channel
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) { 
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
    // a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
    // a connection was established with a remote server.
} else if (key.isReadable()) {
    // a channel is ready for reading
} else if (key.isWritable()) {
    // a channel is ready for writing
}
keyIterator.remove();
}
  • 注意
    1)Selector對(duì)象并不會(huì)從自己的SelectedKey集合中自動(dòng)移除SelectedKey實(shí)例,需要在處理完一個(gè)Channel時(shí)調(diào)用keyIterator.remove()方法手動(dòng)移除破加,下一次Channel就緒時(shí)俱恶,Selector會(huì)再將它添加到SelectedKey集合中
    2)SelectionKey.channel()方法返回的channel需要轉(zhuǎn)成具體要處理的類型,如ServerSocketChannel或SocketChannel等
  • wakeup()
  • open()和close()
    使用Selector前調(diào)用Selector.open()打開Selector
    使用Selector后調(diào)用Selector.close()關(guān)閉Selector并使注冊(cè)到該Selector上的所有SelectionKey實(shí)例無效范舀,但通道本身并不會(huì)關(guān)閉

四.NIO實(shí)踐

1.從文件讀取數(shù)據(jù)

  • 讀數(shù)據(jù)步驟
    1)從FileInputStream獲取Channel
    2)創(chuàng)建Buffer
    3)從Channel讀取數(shù)據(jù)到Buffer
FileInputStream fin = new FileInputStream("ReadTest.txt");
FileChanel fc = fim.getChannel();  //獲取通道
ByteBuffer buffer = ByteBuffer.allocate(1024);  //創(chuàng)建緩沖區(qū)
fc.read(buffer);  //從通道讀入數(shù)據(jù)到緩沖區(qū)
  • 寫數(shù)據(jù)步驟
    1)從FileOutputStream獲取Channel
    2)創(chuàng)建Buffer合是,并將數(shù)據(jù)放入Buffer
    3)把Buffer中數(shù)據(jù)寫入Channel
FileOutputStream fout = new FileOutputStream("WriteTest.txt");
FileChannel fc = fout.getChannel;  //獲取通道
ByteBuffer buffer = ByteBuffer.allocate(1024);  //創(chuàng)建緩沖區(qū)
for(int i = 0 ; i < message.length ; i++) {
  buffer.put( message[i] );
}    //將數(shù)據(jù)放入緩沖區(qū)
buffer.flip();  //切換緩沖區(qū)為寫模式
fc.write(buffer);  //將緩沖區(qū)內(nèi)容寫入通道
  • 讀寫結(jié)合例程
/**
 * 用java NIO api拷貝文件
 * @param src
 * @param dst
 * @throws IOException
 */
public static void copyFileUseNIO(String src,String dst) throws IOException{
    //聲明源文件和目標(biāo)文件
            FileInputStream fi=new FileInputStream(new File(src));
            FileOutputStream fo=new FileOutputStream(new File(dst));
            //獲得傳輸通道channel
            FileChannel inChannel=fi.getChannel();
            FileChannel outChannel=fo.getChannel();
            //獲得容器buffer
            ByteBuffer buffer=ByteBuffer.allocate(1024);
            while(true){
                //判斷是否讀完文件
                int eof =inChannel.read(buffer);
                if(eof==-1){
                    break;  
                }
                //重設(shè)一下buffer的position=0,limit=position
                buffer.flip();
                //開始寫
                outChannel.write(buffer);
                //寫完要重置buffer锭环,重設(shè)position=0,limit=capacity
                buffer.clear();
            }
            inChannel.close();
            outChannel.close();
            fi.close();
            fo.close();
}     

2.網(wǎng)絡(luò)Socket使用NIO

  • 步驟

  • 例程

  public void client() throws Exception{
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 3000));
        socketChannel.configureBlocking(false);
        
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()){
            String str = scanner.next();
            buffer.put(str.getBytes());
            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();
        }
public void server() throws Exception {
        // 獲取通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 設(shè)置為非阻塞
        serverChannel.configureBlocking(false);
        // 綁定端口
        serverChannel.bind(new InetSocketAddress(3000));

        // 創(chuàng)建連接器
        Selector selector = Selector.open();
        // 將連接器注冊(cè)到channel聪全,并設(shè)置監(jiān)聽事件(接受事件)
        // SelectionKey.OP_CONNECT:鏈接狀態(tài)
        // SelectionKey.OP_READ:讀狀態(tài)
        // SelectionKey.OP_WRITE:寫狀態(tài)
        // SelectionKey.OP_ACCEPT:接受狀態(tài),當(dāng)接受準(zhǔn)備就緒辅辩,開始進(jìn)行下一步操作
        // 通過 | 進(jìn)行鏈接可以監(jiān)聽多個(gè)狀態(tài)
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 輪尋獲取選擇器上已經(jīng)準(zhǔn)備就緒的狀態(tài)
        while (selector.select() > 0) {
            // 獲取所有的監(jiān)聽Key
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                if (key.isAcceptable()) {
                    // 若獲取狀態(tài)就緒难礼,就獲取客戶端的鏈接
                    SocketChannel clientChannel = serverChannel.accept();
                    // 將客戶端的鏈接設(shè)置為非阻塞狀態(tài)
                    clientChannel.configureBlocking(false);
                    // 給該通道注冊(cè)到選擇器上,并設(shè)置狀態(tài)為讀就緒
                    clientChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int len = 0;
                    while ((len = channel.read(buffer)) > 0) {
                        buffer.flip();
                        System.out.println(new String(buffer.array(), 0, len));
                        buffer.clear();
                    }
                }
                // 取消處理完了的選擇建
                iterator.remove();
            }
        }
    }

愿將腰下劍,直為斬樓蘭

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末玫锋,一起剝皮案震驚了整個(gè)濱河市蛾茉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌撩鹿,老刑警劉巖谦炬,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異节沦,居然都是意外死亡键思,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門甫贯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吼鳞,“玉大人,你說我怎么就攤上這事获搏±堤酰” “怎么了失乾?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)纬乍。 經(jīng)常有香客問我碱茁,道長(zhǎng),這世上最難降的妖魔是什么仿贬? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任纽竣,我火速辦了婚禮,結(jié)果婚禮上茧泪,老公的妹妹穿的比我還像新娘蜓氨。我一直安慰自己,他們只是感情好队伟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布穴吹。 她就那樣靜靜地躺著,像睡著了一般嗜侮。 火紅的嫁衣襯著肌膚如雪港令。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天锈颗,我揣著相機(jī)與錄音顷霹,去河邊找鬼。 笑死击吱,一個(gè)胖子當(dāng)著我的面吹牛淋淀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播覆醇,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼朵纷,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了叫乌?” 一聲冷哼從身側(cè)響起柴罐,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤徽缚,失蹤者是張志新(化名)和其女友劉穎憨奸,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凿试,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡排宰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了那婉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片板甘。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖详炬,靈堂內(nèi)的尸體忽然破棺而出盐类,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布在跳,位于F島的核電站枪萄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏猫妙。R本人自食惡果不足惜瓷翻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望割坠。 院中可真熱鬧齐帚,春花似錦、人聲如沸彼哼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽敢朱。三九已至饥伊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蔫饰,已是汗流浹背琅豆。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留篓吁,地道東北人茫因。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像杖剪,于是被迫代替她去往敵國(guó)和親冻押。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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

  • Java NIO(New IO)是從Java 1.4版本開始引入的一個(gè)新的IO API盛嘿,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,555評(píng)論 1 143
  • Java NIO(New IO)是從Java 1.4版本開始引入的一個(gè)新的IO API洛巢,可以替代標(biāo)準(zhǔn)的Java I...
    編碼前線閱讀 2,268評(píng)論 0 5
  • Java NIO(New IO)是從Java 1.4版本開始引入的一個(gè)新的IO API,可以替代標(biāo)準(zhǔn)的Java I...
    zhisheng_blog閱讀 1,120評(píng)論 0 7
  • # Java NIO # Java NIO屬于非阻塞IO次兆,這是與傳統(tǒng)IO最本質(zhì)的區(qū)別稿茉。傳統(tǒng)IO包括socket和文...
    Teddy_b閱讀 595評(píng)論 0 0
  • 1.8班 劉惠丹 不浮,不躁芥炭,不急漓库,不慢, 忘卻永琢磨不透的世界园蝠。 初倚樹下渺蒿,側(cè)隱寂寥, 心謐如水彪薛,凄如苔茂装。 方才...
    俊聰?shù)っ?/span>閱讀 359評(píng)論 0 1