快學Java NIO


Java NIO Tutorial 地址:http://tutorials.jenkov.com/java-nio/index.html

Java NIO系列教程譯文地址:http://ifeve.com/java-nio-all/

以下是我拜讀過程中摘抄的部分內容,並且加了一些內容、筆記,姑且叫《快學Java NIO》,方便以后再翻閱學習

附上一個Java NIO實現的demo,多人網絡聊天室

 

Java NIO 由以下幾個核心部分組成:

  • Channels
  • Buffers
  • Selectors

基本上,所有的 IO 在NIO 中都從一個Channel 開始。Channel 有點像流。 數據可以從Channel讀到Buffer中,也可以從Buffer 寫到Channel中。

 

Channel

FileChannel 從文件中讀寫數據。

DatagramChannel 能通過UDP讀寫網絡中的數據。

SocketChannel 能通過TCP讀寫網絡中的數據。

ServerSocketChannel可以監聽新進來的TCP連接,像Web服務器那樣。對每一個新進來的連接都會創建一個SocketChannel。

下面是一個FileChannel的示例

public class FileChannelTest {
    public static void main(String[] args) throws IOException {
        RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
        FileChannel inChannel = aFile.getChannel();
     
//涉及到的buffer的方法稍后解釋 ByteBuffer buf
= ByteBuffer.allocate(48); int bytesRead = inChannel.read(buf); while (bytesRead != -1) { //make buffer ready for read buf.flip(); while (buf.hasRemaining()) { System.out.print((char) buf.get());// read 1 byte at a time }
buf.clear();//buf.compact();也可以 bytesRead
= inChannel.read(buf); } aFile.close(); } }

 

Buffer

為了理解Buffer的工作原理,需要熟悉它的三個屬性:

  • capacity
  • position
  • limit

在寫模式下,Buffer的limit表示你最多能往Buffer里寫多少數據。 寫模式下,limit等於Buffer的capacity。

當切換Buffer到讀模式時, limit表示你最多能讀到多少數據。因此,當切換Buffer到讀模式時,limit會被設置成寫模式下的position值。換句話說,你能讀到之前寫入的所有數據(limit被設置成已寫數據的數量,這個值在寫模式下就是position)

 

clear方法就是讓position設回0,limit與capacity相等。

  public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

 

flip方法將Buffer從寫模式切換到讀模式。調用flip()方法會將position設回0,並將limit設置成之前position的值。

  public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

 

compact()方法將所有未讀的數據拷貝到Buffer起始處。然后將position設到最后一個未讀元素正后面。limit屬性依然像clear()方法一樣,設置成capacity。現在Buffer准備好寫數據了,但是不會覆蓋未讀的數據。

  public ByteBuffer compact() {

        System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
        position(remaining());
        limit(capacity());
        discardMark();
        return this;

    }

 

 Scatter/Gather

scatter/gather用於描述從Channel中讀取或者寫入到Channel的操作

分散(scatter)從Channel中讀取是指在讀操作時將讀取的數據寫入多個buffer中。因此,Channel將從Channel中讀取的數據“分散(scatter)”到多個Buffer中。
聚集(gather)寫入Channel是指在寫操作時將多個buffer的數據寫入同一個Channel,因此,Channel 將多個Buffer中的數據“聚集(gather)”后發送到Channel。

 

應用場景:例如傳輸一個由消息頭和消息體組成的消息,你可能會將消息體和消息頭分散到不同的buffer中,這樣你可以方便的處理消息頭和消息體

 

Scattering Reads在移動下一個buffer前,必須填滿當前的buffer,這也意味着它不適用於動態消息(譯者注:消息大小不固定)。

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

ByteBuffer[] bufferArray = { header, body };

channel.read(bufferArray);

 

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

//write data into buffers

ByteBuffer[] bufferArray = { header, body };

channel.write(bufferArray);

 

FileChannel的transferFrom()方法可以將數據從源通道傳輸到FileChannel中,下面是一個簡單的例子:

RandomAccessFile fromFile = new RandomAccessFile("data/fromFile.txt", "rw");
FileChannel      fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("data/toFile.txt", "rw");
FileChannel      toChannel = toFile.getChannel();

long position = 0;
long count = fromChannel.size();

//toChannel.transferFrom(fromChannel, position, count);也可以
fromChannel.transferTo(position, count, toChannel);

 

Selector

Selector(選擇器)是Java NIO中能夠檢測一到多個NIO通道,並能夠知曉通道是否為諸如讀寫事件做好准備的組件。這樣,一個單獨的線程可以管理多個channel,從而管理多個網絡連接,Selector能夠處理多個通道。

 

Selector selector = Selector.open();
//FileChannel不能切換到非阻塞模式,所以這邊不能使FileChannel channel.configureBlocking(
false);//與Selector一起使用時,Channel必須處於非阻塞模式下 SelectionKey key = channel.register(selector, SelectionKey.OP_READ); //除了注冊讀,還可以注冊connect,accept,read,write事件 while(true) { int readyChannels = selector.select(); //阻塞到至少有一個通道就緒,還有select(long timeout)超時就不阻塞,selectNow()不阻塞,沒有就返回0,當然打斷阻塞還有wakeUp()方法,可以用另外一個線程調用這個方法,操作同一個selector對象即可 if(readyChannels == 0) continue; 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.
     //SelectionKey.channel()方法返回的通道需要轉型成你要處理的類型,如ServerSocketChannel或SocketChannel等
} 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 }
  //Selector不會自己從已選擇鍵集中移除SelectionKey實例。必須在處理完通道時自己移除。下次該通道變成就緒時,Selector會再次將其放入已選擇鍵集中。 keyIterator.remove(); } }

 

現在能看到的情況是,一個請求過來,到Selector這邊,selector從注冊的通道中選擇就緒的通道,然后找到具體的通道處理這個請求。

用一個selector線程來安排所有的channel!

當然為了並發,可以用多個selector,然后不同的channel來注冊。這樣就有了反向代理的感覺,selector就是反向代理服務器上的線程!

(以上是我個人對selector的理解,若理解有誤,請指正)

 

Java NIO與IO

我應該何時使用IO,何時使用NIO呢?在本文中,我會盡量清晰地解析Java NIO和IO的差異、它們的使用場景,以及它們如何影響您的代碼設計。

 

Java NIO與IO之間主要差別

IO                NIO
面向流            面向緩沖
阻塞IO            非阻塞IO
無                選擇器

Java NIO的緩沖導向方法是數據讀取到一個它稍后處理的緩沖區,需要時可在緩沖區中前后移動,這就增加了處理過程中的靈活性。NIO設計中多了buffer,傳統IO如果要這個效果,需要自行定義操作buffer。

Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什么都不會獲取。

Java NIO的選擇器允許一個單獨的線程來監視多個輸入通道,你可以注冊多個通道使用一個選擇器。

 

在IO設計中,我們從InputStream或 Reader逐字節讀取數據。 readline()阻塞直到整行讀完

NIO可讓您只使用一個(或幾個)單線程管理多個通道(網絡連接或文件),但付出的代價是解析數據可能會比從一個阻塞流中讀取數據更復雜。

 

如果需要管理同時打開的成千上萬個連接,這些連接每次只是發送少量的數據,例如聊天服務器,實現NIO的服務器可能是一個優勢。

如果你需要維持許多打開的連接到其他計算機上,如P2P網絡中,使用一個單獨的線程來管理你所有出站連接,可能是一個優勢。

Java NIO: 單線程管理多個連接,如下圖

如果你有少量的連接使用非常高的帶寬,一次發送大量的數據,也許典型的IO服務器實現可能非常契合。

Java IO: 一個典型的IO服務器設計- 一個連接通過一個線程處理,如下圖

至此,基本上Java NIO的大體輪廓已經明白了,鑒於篇幅不要太長,各個具體Channel的介紹移步:快學Java NIO續篇

  • FileChannel
  • SocketChannel
  • ServerSocketChannel
  • Java NIO DatagramChannel
  • Pipe

 

 

 

 


免責聲明!

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



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