Java中的NIO

引言:NIO是Java面試中老生常談的一個話題,No-Block-IO(非阻塞IO);今天專花了一天時間將并發(fā)變成網(wǎng)站上便于NIO的東西全看了一遍烫饼,下面算是自己的一個學(xué)習(xí)筆記,方便以后回顧:

一:NIO簡介

  • 1:基本概念nio 是non-blocking的簡稱试读,在jdk1.4 里提供的新api 。Sun 官方標(biāo)榜的特性如下: 為所有的原始類型提供(Buffer)緩存支持荠耽。字符集編碼解碼解決方案钩骇。
    Channel :一個新的原始I/O 抽象。 支持鎖和內(nèi)存映射文件的文件訪問接口铝量。 提供多路(non-bloking) 非阻塞式的高伸縮性網(wǎng)絡(luò)I/O 倘屹。

  • 2:幾個核心類

    • A:channel
    • B:buffer
    • C:selector
      2.1:channel:NIO中的通道,類比于JavaIO就相當(dāng)于JavaIO中的流慢叨;基本上纽匙,所有的 IO 在NIO 中都從一個Channel 開始。Channel 有點象流拍谐。 數(shù)據(jù)可以從Channel讀到Buffer中烛缔,也可以從Buffer 寫到Channel中。
      2.2:buffer:Buffer 類是 java.nio 的構(gòu)造基礎(chǔ)轩拨。一個 Buffer 對象是固定數(shù)量的數(shù)據(jù)的容器践瓷,其作用是一個存儲器,或者分段運輸區(qū)亡蓉,在這里晕翠,數(shù)據(jù)可被存儲并在之后用于檢索。緩沖區(qū)可以被寫滿或釋放砍濒。對于每個非布爾原始數(shù)據(jù)類型都有一個緩沖區(qū)類淋肾,即 Buffer 的子類有:ByteBuffer硫麻、CharBuffer、DoubleBuffer樊卓、FloatBuffer拿愧、IntBuffer、LongBuffer 和 ShortBuffer简识,是沒有 BooleanBuffer 之說的赶掖。Java NIO 還有個 MappedByteBuffer,用于表示內(nèi)存映射文件
    • 緩沖區(qū)的四個屬性:
    • 1:容量(Capacity):緩沖區(qū)能夠容納的數(shù)據(jù)元素的最大數(shù)量七扰。這一容量在緩沖區(qū)創(chuàng)建時被設(shè)定奢赂,并且永遠(yuǎn)不能被改變。
    • 2:上界(Limit):緩沖區(qū)的第一個不能被讀或?qū)懙脑鼐弊摺>彌_創(chuàng)建時膳灶,limit 的值等于 capacity 的值。假設(shè) capacity = 1024立由,我們在程序中設(shè)置了 limit = 512轧钓,說明,Buffer 的容量為 1024锐膜,但是從 512 之后既不能讀也不能寫毕箍,因此可以理解成,Buffer 的實際可用大小為 512道盏。
    • 3:”位置(Position):下一個要被讀或?qū)懙脑氐乃饕獭N恢脮詣佑上鄳?yīng)的 get() 和 put() 函數(shù)更新。
    • 4:標(biāo)記(Mark):一個備忘位置

    2.3:selector:允許單線程處理多個 Channel荷逞。如果你的應(yīng)用打開了多個連接(通道)媒咳,但每個連接的流量都很低,使用Selector就會很方便种远。


    overview-selectors.png

二:具體用法:

  • 2.1:Buffer的基本用法
    使用Buffer讀寫數(shù)據(jù)一般遵循以下四個步驟:
  • 1:寫入數(shù)據(jù)到Buffer
  • 2:調(diào)用flip()方法
  • 3:從Buffer中讀取數(shù)據(jù)
  • 4:調(diào)用clear()方法或者compact()方法
    當(dāng)向buffer寫入數(shù)據(jù)時涩澡,buffer會記錄下寫了多少數(shù)據(jù)。一旦要讀取數(shù)據(jù)坠敷,需要通過flip()方法將Buffer從寫模式切換到讀模式妙同。在讀模式下,可以讀取之前寫入到buffer的所有數(shù)據(jù)。
    一個小Demo:
public class deme1Nio {
    public static void main(String[] args) {
        File file = new File("F:\\1.txt");
        try {
            RandomAccessFile randomfile  = new RandomAccessFile(file, "rw");
            FileChannel channel = randomfile.getChannel();
            ByteBuffer buff = ByteBuffer.allocate(48);
            int data = channel.read(buff);
            while(data!=-1){
                //去讀到文件的最后位置,安字節(jié)算的努咐;換行:算兩個字節(jié)
                int pos = buff.position();
                System.out.println(",pos="+pos);
                System.out.println(data);
                //切換到讀模式
                buff.flip();
                while(buff.hasRemaining()){ 
                    byte[] bytes = new byte[pos];
                    buff.get(bytes);
                    System.out.print(new String(bytes,"GBK"));
                }
                buff.clear();
                data = channel.read(buff);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
  • 2.2:聚合和分散:
  • 聚合:寫入Channel是指在寫操作時將多個buffer的數(shù)據(jù)寫入同一個Channel暗膜,因此,Channel 將多個Buffer中的數(shù)據(jù)“聚集(gather)”后發(fā)送到Channel。
  • 分撒:從Channel中讀取是指在讀操作時將讀取的數(shù)據(jù)寫入多個buffer中艰匙。因此蹭越,Channel將從Channel中讀取的數(shù)據(jù)“分散(scatter)”到多個Buffer中拖陆。
  • 應(yīng)用:讀取報文頭信息和報文體信息時弛槐;
File file1 = new File("f:\\1.txt"); 
        FileInputStream in1;
        try {
            in1 = new FileInputStream(file1);
            FileChannel channel1 = in1.getChannel();
             //用來存儲頭部信息
            ByteBuffer head = ByteBuffer.allocate(5);
             //用來存儲區(qū)body信息
            ByteBuffer body = ByteBuffer.allocate(10);
            //將兩個信息整合在一起
            ByteBuffer[] arr = {head,body};
            long data1 = channel1.read(arr);
            while(data1!=-1){
                head.flip();
                while(head.hasRemaining()){ 
                    byte[] he = new byte[head.limit()];
                    head.get(he);
                    System.out.println("頭部信息為"+new String(he,"gbk"));
                }
                body.flip();
                while(body.hasRemaining()){
                    byte[] bo = new byte[body.limit()];
                    body.get(bo);
                    System.out.println("內(nèi)容體信息為"+new String(bo,"gbk"));
                }
                head.compact();
                body.compact();
                data1 = channel1.read(arr);
            }
            
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
    public static void writeCombine(){
        File file1 = new File("f:\\2.txt"); 
        RandomAccessFile in1;
        try {
            in1 = new RandomAccessFile(file1,"rw");
             
            FileChannel channel1 = in1.getChannel();
             //用來存儲頭部信息
            ByteBuffer head = ByteBuffer.allocate(5);
            head.put("99999".getBytes());
             //用來存儲區(qū)body信息
            ByteBuffer body = ByteBuffer.allocate(10);
            body.put("1111111111".getBytes());
            //將兩個信息整合在一起
            ByteBuffer[] arr = {head,body};
            head.flip();
            body.flip();
            channel1.write(arr);
            channel1.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
  • 2.3:通道之間傳輸數(shù)據(jù):
  • transfromFrom()和transfromTo()方法的使用;
  • FileChannel的transferFrom()方法可以將數(shù)據(jù)從源通道傳輸?shù)紽ileChannel中依啰;
  • transferTo()方法將數(shù)據(jù)從FileChannel傳輸?shù)狡渌腸hannel中
public class demo3Transform {
    public static void trasfromF(){
        File  file1 = new File("F:\\1.txt");
        File  file2 = new File("F:\\2.txt");
        try {
            RandomAccessFile rfile1 = new RandomAccessFile(file1, "rw");
            RandomAccessFile rfile2 = new RandomAccessFile(file2, "rw");
            FileChannel channel1 = rfile1.getChannel();
            FileChannel channel2 = rfile2.getChannel();
            long position = channel2.position();
            long size = channel2.size();
            //將channel2的內(nèi)容復(fù)制到channel1中去乎串;(從1的position的位置開始)
            channel1.transferFrom(channel2, 2, size);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public static void trasfromT(){
        File  file1 = new File("F:\\1.txt");
        File  file2 = new File("F:\\2.txt");
        try {
            RandomAccessFile rfile1 = new RandomAccessFile(file1, "rw");
            RandomAccessFile rfile2 = new RandomAccessFile(file2, "rw");
            FileChannel channel1 = rfile1.getChannel();
            FileChannel channel2 = rfile2.getChannel();
            long position = channel2.position();
            long size = channel2.size();
            //將channel1的內(nèi)容復(fù)制到channel2中去;(從2的position的位置開始速警,賦值2那么長)
            channel1.transferTo(position, size, channel2);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
      public static void main(String[] args) {
          demo3Transform.trasfromT();
      }
}
  • 2.4:selector的使用叹誉;
    僅用單個線程來處理多個Channels的好處是,只需要更少的線程來處理通道闷旧。事實上长豁,可以只用一個線程處理所有的通道。對于操作系統(tǒng)來說忙灼,線程之間上下文切換的開銷很大匠襟,而且每個線程都要占用系統(tǒng)的一些資源(如內(nèi)存)。因此该园,使用的線程越少越好酸舍。
    但是,需要記住里初,現(xiàn)代的操作系統(tǒng)和CPU在多任務(wù)方面表現(xiàn)的越來越好啃勉,所以多線程的開銷隨著時間的推移,變得越來越小了双妨。實際上璧亮,如果一個CPU有多個內(nèi)核,不使用多任務(wù)可能是在浪費CPU能力斥难。不管怎么說,關(guān)于那種設(shè)計的討論應(yīng)該放在另一篇不同的文章中帘饶。在這里哑诊,只要知道使用Selector能夠處理多個通道就足夠了。
  • 1:Selector的創(chuàng)建
    通過調(diào)用Selector.open()方法創(chuàng)建一個Selector及刻,如下:
    elector selector = Selector.open();
  • 2:向Selector注冊通道
    為了將Channel和Selector配合使用镀裤,必須將channel注冊到selector上。通過SelectableChannel.register()方法來實現(xiàn)缴饭,如下:
    • 1:channel.configureBlocking(false);
    • 2:SelectionKey key = channel.register(selector,Selectionkey.OP_READ);
      與Selector一起使用時暑劝,Channel必須處于非阻塞模式下。這意味著不能將FileChannel與Selector一起使用颗搂,因為FileChannel不能切換到非阻塞模式担猛。而套接字通道都可以。
  • 3:SelectionKey
    在上一小節(jié)中,當(dāng)向Selector注冊Channel時傅联,register()方法會返回一個SelectionKey對象先改。這個對象包含了一些屬性:
    • interest集合
    • ready集合
    • Channel
    • Selector
    • 附加的對象(可選)
      下面我會描述這些屬性。
    • interest集合
      可以通過SelectionKey讀寫interest集合
    • ready集合
      ready 集合是通道已經(jīng)準(zhǔn)備就緒的操作的集合蒸走。在一次選擇(Selection)之后仇奶,你會首先訪問這個ready的set集合。
    • 附加的對象
      可以將一個對象或者更多信息附著到SelectionKey上比驻,這樣就能方便的識別某個給定的通道该溯。
    • 通過Selector選擇通道
      一旦向Selector注冊了一或多個通道,就可以調(diào)用幾個重載的select()方法别惦。這些方法返回你所感興趣的事件(如連接狈茉、接受、讀或?qū)懀┮呀?jīng)準(zhǔn)備就緒的那些通道步咪。換句話說论皆,如果你對“讀就緒”的通道感興趣,select()方法會返回讀事件已經(jīng)就緒的那些通道猾漫。
      下面是select()方法:
      • int select()
      • int select(long timeout)
      • int selectNow()
      • select()阻塞到至少有一個通道在你注冊的事件上就緒了点晴。
      • select(long timeout)和select()一樣,除了最長會阻塞timeout毫秒(參數(shù))悯周。
      • selectNow()不會阻塞粒督,不管什么通道就緒都立刻返回
        select()方法返回的int值表示有多少通道已經(jīng)就緒
    • selectedKeys()
      一旦調(diào)用了select()方法,并且返回值表明有一個或更多個通道就緒了禽翼,然后可以通過調(diào)用selector的selectedKeys()方法屠橄,訪問“已選擇鍵集(selected key set)”中的就緒通道
Set selectedKeys = selector.selectedKeys();
Iterator 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();
}

四:IO和NIO

1:區(qū)別:


IO和NIO的區(qū)別.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市闰挡,隨后出現(xiàn)的幾起案子锐墙,更是在濱河造成了極大的恐慌,老刑警劉巖长酗,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溪北,死亡現(xiàn)場離奇詭異,居然都是意外死亡夺脾,警方通過查閱死者的電腦和手機(jī)之拨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咧叭,“玉大人蚀乔,你說我怎么就攤上這事》撇纾” “怎么了吉挣?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵派撕,是天一觀的道長。 經(jīng)常有香客問我听想,道長腥刹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任汉买,我火速辦了婚禮衔峰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛙粘。我一直安慰自己垫卤,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布出牧。 她就那樣靜靜地躺著穴肘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舔痕。 梳的紋絲不亂的頭發(fā)上评抚,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音伯复,去河邊找鬼慨代。 笑死,一個胖子當(dāng)著我的面吹牛啸如,可吹牛的內(nèi)容都是我干的侍匙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼叮雳,長吁一口氣:“原來是場噩夢啊……” “哼想暗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起帘不,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤说莫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后寞焙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唬滑,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年棺弊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片擒悬。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡模她,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出懂牧,到底是詐尸還是另有隱情侈净,我是刑警寧澤尊勿,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站畜侦,受9級特大地震影響元扔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜旋膳,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一澎语、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧验懊,春花似錦擅羞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至碱工,卻和暖如春娃承,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怕篷。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工历筝, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匙头。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓漫谷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蹂析。 傳聞我的和親對象是個殘疾皇子舔示,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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

  • Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,536評論 1 143
  • 前言: 之前的文章《Java文件IO常用歸納》主要寫了Java 標(biāo)準(zhǔn)IO要注意的細(xì)節(jié)和技巧电抚,由于網(wǎng)上各種學(xué)習(xí)途徑惕稻,...
    androidjp閱讀 2,894評論 0 22
  • 簡介 Java NIO 是由 Java 1.4 引進(jìn)的異步 IO.Java NIO 由以下幾個核心部分組成: Ch...
    永順閱讀 1,786評論 0 15
  • 臨近畢業(yè),諸多雜事將大塊兒完整時間分割蝙叛,足足約了三個月之后俺祠,2017年5月18日20:40,四個人的空閑時間終于湊...
    澍芃閱讀 291評論 0 1
  • 一|到達(dá) 到三亞的那一天是個十足的好日子借帘。陽光明媚蜘渣,微風(fēng)暖暖。我在機(jī)場等待一年多沒見的F大寶肺然。等待總是漫長的蔫缸,待她...
    車?yán)遄覮閱讀 616評論 3 2