NIO

http://tutorials.jenkov.com/java-nio/index.html 原文地址
Java NIO (New IO) is an alternative IO API for Java (from Java 1.4), meaning alternative to the standard Java IO and Java Networking API's. Java NIO offers a different way of working with IO than the standard IO API's

  • Java NIO: Channels and Buffers
    標準的IO基于字節流和字符流進行操作的,而NIO是基于通道(Channel)和緩沖區(Buffer)進行操作,數據總是從通道讀取到緩沖區中,或者從緩沖區寫入到通道中。

  • Java NIO: Non-blocking IO ( 非阻塞IO )
    Java NIO可以讓你非阻塞的使用IO,例如:當線程從通道讀取數據到緩沖區時,線程還是可以進行其他事情。當數據被寫入到緩沖區時,線程可以繼續處理它。從緩沖區寫入通道也類似。

  • Java NIO: Selectors ( 選擇器 )
    Java NIO可以讓你非阻塞的使用IO,例如:當線程從通道讀取數據到緩沖區時,線程還是可以進行其他事情。當數據被寫入到緩沖區時,線程可以繼續處理它。從緩沖區寫入通道也類似。

Channel

jdk中的注釋 :

A nexus for I/O operations.
A channel represents an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing.

Java NIO Channels 和Stream類似,但又有些不同:
Channel既可以從通道中讀取數據,又可以寫數據到通道。但流通常是只讀或只寫的。
Channel可以異步地讀寫。
Channel總是讀數據到一個Buffer,或者從一個Buffer中寫數據到Channel。如下圖所示:


ReadAndWrite.png

Channel的實現:

ChannelImple.png

FileChannel : 從文件中讀寫數據
DatagramChannel : 通過UDP讀寫網絡中的數據
SocketChannel : 能通過TCP讀寫網絡中的數據
ServerSocketChannel : 可以監聽指定端口TCP連接,對每一個新進來的連接都創建一個SocketChannel。

Buffer

使用Buffer讀寫數據一般遵循四個步驟 : 1. Channel.Read數據到Buffer中,或者直接Buffer.put(數據)。 2. 調用 Buffer.flip()切換Buffer到讀模式。 3. Buffer.get()讀取字節或者Channel.write(buffer)將Buffer中數據寫入Channel。4. 調用Buffer.clear()或者Buffer.compact()。clear()方法清空整個緩沖區。compact()方法只會清除已讀過的數據,未讀數據移到Buffer的起始處。 下面是一個負值文件的example :

        String infile = "nio/src/main/resources/CopyFile.java";
        String outfile = "nio/src/main/resources/CopyFile.java.copy";
        File file = new File(infile);

        FileInputStream fin = new FileInputStream(file);
        FileOutputStream fout = new FileOutputStream(outfile);

        FileChannel finChannel = fin.getChannel();
        FileChannel foutChannel = fout.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        while (true) {
            buffer.clear();

            int read = finChannel.read(buffer);
            if (read == -1) break;

            buffer.flip(); 
            foutChannel.write(buffer);
        }
  • Buffer 的 capacity position limit

capacity : Buffer內存塊的上限值,一旦Buffer滿了需要清空數據才能繼續往Buffer寫數據。
position : 寫模式時,標識當前寫的位置,初始值為0,最大值小于capacity。讀模式時,標識當前讀位置。寫模式切換到讀模式,position會被重置為0。
limit : 在寫模式下,limit==capacity,讀模式下,limit表示能讀到的數據最高位置。因此,切換Buffer到讀模式時,limit會被設置成寫模式下position值。

  • Buffer的類型

    BufferImpl.png

  • Buffer api

      //Buffer內存分配
      ByteBuffer buffer = ByteBuffer.allocate(1024);    //分配一個1024capacity的ByteBuffer
      
      //向Buffer中寫入數據
      finChannel.read(buffer);  //read data from channel into buffer
      buffer.put((byte) 2); //put data into buffer

      //flip()
      buffer.flip(); //將Buffer從寫模式切換到讀模式

      //從Buffer中讀取數據
      buffer.get(); //直接從Buffer中get數據
      foutChannel.write(buffer); //將Buffer中數據寫入到Channel

      //rewind() 將position設置回0,可以用此重復讀Buffer。
      buffer.rewind();

      //clear() 與compact()
      buffer.clear() //position設置回0,limit等于capacity,進入寫模式。
      buffer.compact() //將未讀數據拷貝到起始處,position設置到沒有元素的位置,新寫入數據不會覆蓋未讀數據。

      //mark() reset()
      buffer.mark(); //標記當前position
      buffer.get();  //get()一次 position后移
      buffer.reset(); //reset()position 復位到mark的位置

      //equals() 與 compareTo()
      buffer1.equals(buffer2); //比較Buffer重的剩余未讀元素(position到limit之間的元素)相等
      buffer1.compareTo(buffer2);  //比較元素 第一個不相等的元素比較大小 或者 元素全部相等 元素個數多少比較

Scatter/Gather

A scattering read from a channel is a read operation that reads data into more than one buffer.
分散讀是指從Channel中讀取數據寫入到多個Buffer中,下面是示例圖和Code :


ScatteringReads.png
        ByteBuffer header = ByteBuffer.allocate(128);
        ByteBuffer body = ByteBuffer.allocate(1024);

        ByteBuffer[] buffers = {header, body};
        channel.read(buffers);

A gathering write to a channel is a write operation that writes data from multiple buffers into a single channel.
聚集寫是指從多個Buffer中向一個Channel里寫數據,下面是示例圖和Code :


GatheringWrites.png
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = { header, body };

channel.write(bufferArray);

Channel to Channel Transfers

  • transFrom()

In Java NIO you can transfer data directly from one channel to another, if one of the channels is a FileChannel. The FileChannel class has a transferTo() and a transferFrom() method which does this for you.

        FileChannel finChannel = fin.getChannel();
        FileChannel foutChannel = fout.getChannel();

        long position = 0;
        long size = finChannel.size();

        foutChannel.transferFrom(finChannel,position,size);
  • transTo

The transferTo() method transfer from a FileChannel into some other channel. Here is a simple example:

        FileChannel finChannel = fin.getChannel();
        FileChannel foutChannel = fout.getChannel();

        long position = 0;
        long size = finChannel.size();

        finChannel.transferTo(1,size,foutChannel);

Selector

A Selector is a Java NIO component which can examine one or more NIO Channel's, and determine which channels are ready for e.g. reading or writing. This way a single thread can manage multiple channels, and thus multiple network connections.

  • 為什么使用Selector

僅用單個線程來處理多個Channels的好處是,只需要更少的線程來處理通道。事實上,可以只用一個線程處理所有的通道。對于操作系統來說,線程之間上下文切換的開銷很大,而且每個線程都要占用系統的一些資源(如內存)。因此,使用的線程越少越好。但是,需要記住,現代的操作系統和CPU在多任務方面表現的越來越好,所以多線程的開銷隨著時間的推移,變得越來越小了。實際上,如果一個CPU有多個內核,不使用多任務可能是在浪費CPU能力。

  • Selector api
    SocketChannel channel = SocketChannel.open();
        //創建Selector
        Selector selector = Selector.open();

        //向Selector注冊通道
        //Channel必須處于非阻塞模式下才能與Selector使用,這意味著FileChannel不能使用Selector
        channel.configureBlocking(false);

        //將Channel注冊到Selector上 并表示是可讀的 返回key
        SelectionKey key = channel.register(selector, (SelectionKey.OP_READ | SelectionKey.OP_WRITE));

//        通道四種interest狀態 如果不止一種狀態可以用 | 傳多個
//        SelectionKey.OP_READ;
//        SelectionKey.OP_ACCEPT;
//        SelectionKey.OP_CONNECT;
//        SelectionKey.OP_WRITE;

        //SelectionKey interestSet
        int interestOps = key.interestOps(); //interest集合 返回了之前注冊的 OP_READ | OP_WRITE == 5
        int readyOps = key.readyOps(); //ready集合 是已經準備就緒的操作集合 一次Selection后會首先訪問這個ready set

        //可通過key獲取 channel 和 selector
        SelectableChannel selectableChannel = key.channel();
        Selector selector1 = key.selector();

        //可以給key添加附加對象 標識區分key
        Object attach = key.attach(new Buffer[]{});
        Object attachment = key.attachment();

        //通過Selector選擇通道
        //向Selector注冊了通道后,可以調用select()方法,返回的int值表示多少通道已經就緒,這是個同步阻塞方法,若無就緒通道會等待直到有就緒通道.
        //selector.wakeup(); 可以喚醒阻塞select()方法 立馬返回
        int ready = selector.select();
        //在確認ready channel > 0 后 獲取就緒通道注冊的keys
        Set<SelectionKey> selectionKeys = selector.selectedKeys();

        //當像Selector注冊Channel時,Channel.register()方法會返回一個SelectionKey 對象。
        //這個對象代表了注冊到該Selector的通道。可以通過SelectionKey的selectedKeySet()方法訪問這些對象。
        Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
        while (keyIterator.hasNext()){
            SelectionKey next = keyIterator.next();
            if (next.isAcceptable()){
                SelectableChannel readyAcceptChannel = next.channel(); //獲取通道
            }
            keyIterator.remove();
        }

        selector.close();//關閉selector 注冊的keys全部失效 channel不會失效

FileChannel

A Java NIO FileChannel is a channel that is connected to a file. Using a file channel you can read data from a file, and write data to a file.

SocketChannel

A Java NIO SocketChannel is a channel that is connected to a TCP network socket.

        //Open a SocketChannel
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("localhost",8080));

        //Reading data from a SocketChannel into Buffer
        ByteBuffer buffer = ByteBuffer.allocate(48);
        socketChannel.read(buffer);

        //Writing data from Buffer into SocketChannel
        buffer.flip();
        while (buffer.hasRemaining()){
            socketChannel.write(buffer);
        }

        //Configure the SocketChannel be non-blocking mode,the method may return before a connection is established.
        //Then call the finishConnect() method to determine whether connection is established.
        socketChannel.configureBlocking(false);
        socketChannel.connect(new InetSocketAddress("localhost",8080));
        if (socketChannel.finishConnect()) {
            //TODO
        }

        //Closing a SocketChannel
        socketChannel.close();

ServerSocketChannel

A Java NIO ServerSocketChannel is a channel that can listen for incoming TCP connections.

        //Opening a ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //Binds the {@code ServerSocket} to a specific address (IP address and port number).
        serverSocketChannel.socket().bind(new InetSocketAddress(8080));

        while (true){
            //Listening for Incoming Connections
            //The accept() method blocks until an incoming connection arrives,and returns a SocketChannel.
            SocketChannel socketChannel = serverSocketChannel.accept();
            //TODO ...

            //In non-blocking mode the accept() method returns immediately,
            // if no incoming connection had arrived,you will have to check if the returned channel is null.
            serverSocketChannel.configureBlocking(false);
            if (socketChannel!=null){
                //TODO ...
            }

            //Closing a ServerSocketChannel
            serverSocketChannel.close();
        }

DatagramChannel

A Java NIO DatagramChannel is a channel that can send and receive UDP packets.

Pipe

A Java NIO Pipe is a one-way data connection between two threads. A Pipe has a source channel and a sink channel. You write data to the sink channel. This data can then be read from the source channel.Here is an illustration of the Pipe principle :


Pipe Internals.png
        //Creating a Pipe.
        Pipe pipe = Pipe.open();

        //Writing to a Pipe.
        Pipe.SinkChannel sinkChannel = pipe.sink();//To write to a pipe you need to access the sink channel.

        ByteBuffer buffer = ByteBuffer.allocate(48);
        buffer.put(new byte[]{1,2,3,4,5});
        buffer.flip();

        while (buffer.hasRemaining()){
            sinkChannel.write(buffer); //Wiriting data from buffer into pipe.sinkChannel
        }

        //Reading from a Pipe
        Pipe.SourceChannel sourceChannel = pipe.source(); //To read from a pipe you need to access the channel.
        ByteBuffer buffer2 = ByteBuffer.allocate(48);
        int read = sourceChannel.read(buffer2);//Reading data from pipe.sourceChannel into buffer.
        buffer.flip();
        buffer2.flip();
        System.out.println(buffer.equals(buffer2)); //true

NIO和IO比較

Differences.png
  • Stream VS Buffer

Java NIO和IO之間第一個最大的區別是,IO是面向流的,NIO是面向緩沖區的。 Java IO面向流意味著每次從流中讀一個或多個字節,直至讀取所有字節,它們沒有被緩存在任何地方。此外,它不能前后移動流中的數據。如果需要前后移動從流中讀取的數據,需要先將它緩存到一個緩沖區。 Java NIO的緩沖導向方法略有不同。數據讀取到一個它稍后處理的緩沖區,需要時可在緩沖區中前后移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩沖區中包含所有您需要處理的數據。而且,需確保當更多的數據讀入緩沖區時,不要覆蓋緩沖區里尚未處理的數據。

  • Blocking vs. Non-blocking IO

Java IO的各種流是阻塞的。這意味著,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再干任何事情了。 Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什么都不會獲取。而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閑時間用于在其它通道上執行IO操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道(channel)。

  • Selectors

ava NIO的選擇器允許一個單獨的線程來監視多個輸入通道,你可以注冊多個通道使用一個選擇器,然后使用一個單獨的線程來“選擇”通道:這些通道里已經有可以處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容