Java NIO2:NIO概述


一、概述

     從JDK1.4開始,Java提供了一系列改進的輸入/輸出處理的新特性,被統稱為NIO(即New I/O)。新增了許多用於處理輸入輸出的類,這些類都被放在java.nio包及子包下,並且對原java.io包中的很多類進行改寫,新增了滿足NIO的功能。NIO采用內存映射文件的方式來處理輸入輸出,NIO將文件或文件的一段區域映射到內存中,這樣就可以像訪問內存一樣訪問文件了。

NIO 與原來的 I/O 有同樣的作用和目的,但是它使用不同的方式? I/O。塊 I/O 的效率可以比流 I/O 高許多。

流與塊的比較

     原來的 I/O 庫(在 java.io.*中) 與 NIO 最重要的區別是數據打包和傳輸的方式。正如前面提到的,原來的 I/O 以流的方式處理數據,而 NIO 以塊的方式處理數據。

     面向流 的 I/O 系統一次一個字節地處理數據。一個輸入流產生一個字節的數據,一個輸出流消費一個字節的數據。為流式數據創建過濾器非常容易。鏈接幾個過濾器,以便每個過濾器只負責單個復雜處理機制的一部分,這樣也是相對簡單的。不利的一面是,面向流的 I/O 通常相當慢。

     一個 面向塊 的 I/O 系統以塊的形式處理數據。每一個操作都在一步中產生或者消費一個數據塊。按塊處理數據比按(流式的)字節處理數據要快得多。但是面向塊的 I/O 缺少一些面向流的 I/O 所具有的優雅性和簡單性。

     在NIO中有幾個核心對象需要掌握:緩沖區(Buffer)、通道(Channel)、選擇器(Selector)。

二、緩沖區Buffer

     緩沖區實際上是一個容器對象,更直接的說,其實就是一個數組,在NIO庫中,所有數據都是用緩沖區處理的。在讀取數據時,它是直接讀到緩沖區中的; 在寫入數據時,它也是寫入到緩沖區中的;任何時候訪問 NIO 中的數據,都是將它放到緩沖區中。而在面向流I/O系統中,所有數據都是直接寫入或者直接將數據讀取到Stream對象中。具體看下面這張圖就理解了:

    上面的圖描述了從一個客戶端向服務端發送數據,然后服務端接收數據的過程。客戶端發送數據時,必須先將數據存入Buffer中,然后將Buffer中的內容寫入通道。服務端這邊接收數據必須通過Channel將數據讀入到Buffer中,然后再從Buffer中取出數據來處理。

    在NIO中,所有的緩沖區類型都繼承於抽象類Buffer,最常用的就是ByteBuffer,對於Java中的基本類型,基本都有一個具體Buffer類型與之相對應,它們之間的繼承關系如下圖所示:

下面是一個簡單使用IntBuffer的例子:

package com.demo.nio;

import java.nio.IntBuffer;

public class TestIntBuffer {

    public static void main(String[] args) {
        // 分配新的int緩沖區,參數為緩沖區容量
        // 新緩沖區的當前位置將為零,其界限(限制位置)將為其容量。它將具有一個底層實現數組,其數組偏移量將為零。
        IntBuffer buffer = IntBuffer.allocate(8);

        for (int i = 0; i < buffer.capacity(); ++i) {
            int j = 2 * (i + 1);
            // 將給定整數寫入此緩沖區的當前位置,當前位置遞增
            buffer.put(j);
        }

        // 重設此緩沖區,將限制設置為當前位置,然后將當前位置設置為0
        buffer.flip();

        // 查看在當前位置和限制位置之間是否有元素
        while (buffer.hasRemaining()) {
            // 讀取此緩沖區當前位置的整數,然后當前位置遞增
            int j = buffer.get();
            System.out.print(j + "  ");
        }
    }
}

運行后可以看到:

三、通道Channel

      Channel和傳統IO中的Stream很相似。雖然很相似,但是有很大的區別,主要區別為:通道是雙向的,通過一個Channel既可以進行讀,也可以進行寫;而Stream只能進行單向操作,通過一個Stream只能進行讀或者寫,比如InputStream只能進行讀取操作,OutputStream只能進行寫操作;

      通道是一個對象,通過它可以讀取和寫入數據,當然了所有數據都通過Buffer對象來處理。我們永遠不會將字節直接寫入通道中,相反是將數據寫入包含一個或者多個字節的緩沖區。同樣不會直接從通道中讀取字節,而是將數據從通道讀入緩沖區,再從緩沖區獲取這個字節。

      在NIO中,提供了多種通道對象,而所有的通道對象都實現了Channel接口。它們之間的繼承關系如下圖所示:

 

    Channel(通道)表示到實體如硬件設備、文件、網絡套接字或可以執行一個或多個不同I/O操作的程序組件的開放的連接。所有的Channel都不是通過構造器創建的,而是通過傳統的節點InputStream、OutputStream的getChannel方法來返回響應的Channel。

  Channel中最常用的三個類方法就是map、read和write,其中map方法用於將Channel對應的部分或全部數據映射成ByteBuffer,而read或write方法有一系列的重載形式,這些方法用於從Buffer中讀取數據或向Buffer中寫入數據。

1、使用NIO讀取數據

在前面我們說過,任何時候讀取數據,都不是直接從通道讀取,而是從通道讀取到緩沖區。所以使用NIO讀取數據可以分為下面三個步驟: 
(1). 從FileInputStream獲取Channel 
(2). 創建Buffer 
(3). 將數據從Channel讀取到Buffer中

下面是一個簡單的使用NIO從文件中讀取數據的例子:

import java.io.*;  
import java.nio.*;  
import java.nio.channels.*;  
  
public class Program {  
    public static void main( String args[] ) throws Exception {  
        FileInputStream fin = new FileInputStream("c:\\test.txt");  
          
        // 獲取通道  
        FileChannel fc = fin.getChannel();  
          
        // 創建緩沖區  
        ByteBuffer buffer = ByteBuffer.allocate(1024);  
          
        // 讀取數據到緩沖區  
        fc.read(buffer);  
          
        buffer.flip();  
          
        while (buffer.remaining()>0) {  
            byte b = buffer.get();  
            System.out.print(((char)b));  
        }  
          
        fin.close();  
    }  
}  

2、使用NIO寫入數據

使用NIO寫入數據與讀取數據的過程類似,同樣數據不是直接寫入通道,而是寫入緩沖區,可以分為下面三個步驟:
(1). 從FileInputStream獲取Channel
(2). 創建Buffer
(3). 將數據從Channel寫入到Buffer中

下面是一個簡單的使用NIO向文件中寫入數據的例子:

package com.demo.nio;

import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class Program {
    
    private static final byte message[] = { 83, 111, 109, 101, 32,
        98, 121, 116, 101, 115, 46 };

    public static void main( String args[] ) throws Exception {
        FileOutputStream fout = new FileOutputStream( "c:\\test.txt" );
        
        FileChannel fc = fout.getChannel();
        
        ByteBuffer buffer = ByteBuffer.allocate( 1024 );
        
        for (int i=0; i<message.length; ++i) {
            buffer.put( message[i] );
        }
        
        buffer.flip();
        
        fc.write( buffer );
        
        fout.close();
    }

}

四、選擇器Selector

      Selector類是NIO的核心類,Selector能夠檢測多個注冊的通道上是否有事件發生,如果有事件發生,便獲取事件然后針對每個事件進行相應的響應處理。這樣一來,只是用一個單線程就可以管理多個通道,也就是管理多個連接。這樣使得只有在連接真正有讀寫事件發生時,才會調用函數來進行讀寫,就大大地減少了系統開銷,並且不必為每個連接都創建一個線程,不用去維護多個線程,並且避免了多線程之間的上下文切換導致的開銷。

  與Selector有關的一個關鍵類是SelectionKey,一個SelectionKey表示一個到達的事件,這2個類構成了服務端處理業務的關鍵邏輯。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM