1. 程式人生 > >Java NIO學習筆記---Channel

Java NIO學習筆記---Channel

Java NIO 的核心組成部分:

1.Channels

2.Buffers

3.Selectors

  我們首先來學習Channels(java.nio.channels):

通道

  1)通道基礎

  通道(Channel)是java.nio的第二個主要創新。它們既不是一個擴充套件也不是一項增強,而是全新、極好的Java I/O示例,提供與I/O服務的直接連線。Channel用於在位元組緩衝區和位於通道另一側的實體(通常是一個檔案或套接字)之間有效地傳輸資料。

  channel的jdk原始碼:

package java.nio.channels;
    public interface Channel;
    {
        public boolean isOpen();
        public void close() throws IOException;
    }

  與緩衝區不同,通道API主要由介面指定。不同的作業系統上通道實現(Channel Implementation)會有根本性的差異,所以通道API僅僅描述了可以做什麼。因此很自然地,通道實現經常使用作業系統的原生代碼。通道介面允許您以一種受控且可移植的方式來訪問底層的I/O服務。

  Channel是一個物件,可以通過它讀取和寫入資料。拿 NIO 與原來的 I/O 做個比較,通道就像是流。所有資料都通過 Buffer 物件來處理。您永遠不會將位元組直接寫入通道中,相反,您是將資料寫入包含一個或者多個位元組的緩衝區。同樣,您不會直接從通道中讀取位元組,而是將資料從通道讀入緩衝區,再從緩衝區獲取這個位元組。

Java NIO 的通道類似流,但又有些不同:

  • 既可以從通道中讀取資料,又可以寫資料到通道。但流的讀寫通常是單向的。
  • 通道可以非同步地讀寫。
  • 通道中的資料總是要先讀到一個 Buffer,或者總是要從一個 Buffer 中寫入。

基本上,所有的 IO 在NIO 中都從一個Channel 開始。Channel 有點象流。 資料可以從Channel讀到Buffer中,也可以從Buffer 寫到Channel中。這裡有個圖示: 
                                                      

下面是JAVA NIO中的一些主要Channel的實現: java.nio.channels包中的Channel介面的已知實現類

  • FileChannel:從檔案中讀寫資料。
  • DatagramChannel:能通過UDP讀寫網路中的資料。
  • SocketChannel:能通過TCP讀寫網路中的資料。
  • ServerSocketChannel:可以監聽新進來的TCP連線,像Web伺服器那樣。對每一個新進來的連線都會建立一個SocketChannel。

正如你所看到的,這些通道涵蓋了UDP 和 TCP 網路IO,以及檔案IO。 

  2)通道API   1.檔案通道API   FileChannel類可以實現常用的read,write以及scatter/gather操作,同時它也提供了很多專用於檔案的新方法。這些方法中的許多都是我們所熟悉的檔案操作。  FileChannel類的JDK原始碼:
package java.nio.channels;
    public abstract class FileChannel extends AbstractChannel implements ByteChannel, GatheringByteChannel, ScatteringByteChannel
    {
        // This is a partial API listing
        // All methods listed here can throw java.io.IOException
        public abstract int read (ByteBuffer dst, long position);
        public abstract int write (ByteBuffer src, long position);
        public abstract long size();
        public abstract long position();
        public abstract void position (long newPosition);
        public abstract void truncate (long size);
        public abstract void force (boolean metaData);
        public final FileLock lock();
        public abstract FileLock lock (long position, long size, boolean shared);
        public final FileLock tryLock();
        public abstract FileLock tryLock (long position, long size, boolean shared);
        public abstract MappedByteBuffer map (MapMode mode, long position, long size);
        public static class MapMode;
        public static final MapMode READ_ONLY;
        public static final MapMode READ_WRITE;
        public static final MapMode PRIVATE;
        public abstract long transferTo (long position, long count, WritableByteChannel target);
        public abstract long transferFrom (ReadableByteChannel src, long position, long count);
    } 

  檔案通道總是阻塞式的,因此不能被置於非阻塞模式。現代作業系統都有複雜的快取和預取機制,使得本地磁碟I/O操作延遲很少。網路檔案系統一般而言延遲會多些,不過卻也因該優化而受益。面向流的I/O的非阻塞範例對於面向檔案的操作並無多大意義,這是由檔案I/O本質上的不同性質造成的。對於檔案I/O,最強大之處在於非同步I/O(asynchronous I/O),它允許一個程序可以從作業系統請求一個或多個I/O操作而不必等待這些操作的完成。發起請求的程序之後會收到它請求的I/O操作已完成的通知。

  FileChannel物件是執行緒安全(thread-safe)的。多個程序可以在同一個例項上併發呼叫方法而不會引起任何問題,不過並非所有的操作都是多執行緒的(multithreaded)。影響通道位置或者影響檔案大小的操作都是單執行緒的(single-threaded)。如果有一個執行緒已經在執行會影響通道位置或檔案大小的操作,那麼其他嘗試進行此類操作之一的執行緒必須等待。併發行為也會受到底層的作業系統或檔案系統影響。

  每個FileChannel物件都同一個檔案描述符(file descriptor)有一對一的關係,所以上面列出的API方法與在您最喜歡的POSIX(可移植作業系統介面)相容的作業系統上的常用檔案I/O系統呼叫緊密對應也就不足為怪了。本質上講,RandomAccessFile類提供的是同樣的抽象內容。在通道出現之前,底層的檔案操作都是通過RandomAccessFile類的方法來實現的。FileChannel模擬同樣的I/O服務,因此它的API自然也是很相似的。

  三者之間的方法對比:

FILECHANNELRANDOMACCESSFILEPOSIX SYSTEM CALL
read( ) read( ) read( )
write( ) write( ) write( )
size( ) length( ) fstat( )
position( ) getFilePointer( ) lseek( )
position (long newPosition) seek( ) lseek( )
truncate( ) setLength( ) ftruncate( )
force( ) getFD().sync( ) fsync( )

  2.Socket通道
  新的socket通道類可以執行非阻塞模式並且是可選擇的。這兩個效能可以啟用大程式(如網路伺服器和中介軟體元件)巨大的可伸縮性和靈活性。本節中我們會看到,再也沒有為每個socket連線使用一個執行緒的必要了,也避免了管理大量執行緒所需的上下文交換總開銷。藉助新的NIO類,一個或幾個執行緒就可以管理成百上千的活動socket連線了並且只有很少甚至可能沒有效能損失。所有的socket通道類(DatagramChannel、SocketChannel和ServerSocketChannel)都繼承了位於java.nio.channels.spi包中的AbstractSelectableChannel。這意味著我們可以用一個Selector物件來執行socket通道的就緒選擇(readiness selection)。

  請注意DatagramChannel和SocketChannel實現定義讀和寫功能的介面而ServerSocketChannel不實現。ServerSocketChannel負責監聽傳入的連線和建立新的SocketChannel物件,它本身從不傳輸資料。

  在我們具體討論每一種socket通道前,您應該瞭解socket和socket通道之間的關係。之前的章節中有寫道,通道是一個連線I/O服務導管並提供與該服務互動的方法。就某個socket而言,它不會再次實現與之對應的socket通道類中的socket協議API,而java.net中已經存在的socket通道都可以被大多數協議操作重複使用。

  全部socket通道類(DatagramChannel、SocketChannel和ServerSocketChannel)在被例項化時都會建立一個對等socket物件。這些是我們所熟悉的來自java.net的類(Socket、ServerSocket和DatagramSocket),它們已經被更新以識別通道。對等socket可以通過呼叫socket( )方法從一個通道上獲取。此外,這三個java.net類現在都有getChannel( )方法。

  Socket通道將與通訊協議相關的操作委託給相應的socket物件。socket的方法看起來好像在通道類中重複了一遍,但實際上通道類上的方法會有一些新的或者不同的行為。

  要把一個socket通道置於非阻塞模式,我們要依靠所有socket通道類的公有超級類:SelectableChannel。就緒選擇(readiness selection)是一種可以用來查詢通道的機制,該查詢可以判斷通道是否準備好執行一個目標操作,如讀或寫。非阻塞I/O和可選擇性是緊密相連的,那也正是管理阻塞模式的API程式碼要在SelectableChannel超級類中定義的原因。

  設定或重新設定一個通道的阻塞模式是很簡單的,只要呼叫configureBlocking( )方法即可,傳遞引數值為true則設為阻塞模式,引數值為false值設為非阻塞模式。真的,就這麼簡單!您可以通過呼叫isBlocking( )方法來判斷某個socket通道當前處於哪種模式。

  非阻塞socket通常被認為是服務端使用的,因為它們使同時管理很多socket通道變得更容易。但是,在客戶端使用一個或幾個非阻塞模式的socket通道也是有益處的,例如,藉助非阻塞socket通道,GUI程式可以專注於使用者請求並且同時維護與一個或多個伺服器的會話。在很多程式上,非阻塞模式都是有用的。

  偶爾地,我們也會需要防止socket通道的阻塞模式被更改。API中有一個blockingLock( )方法,該方法會返回一個非透明的物件引用。返回的物件是通道實現修改阻塞模式時內部使用的。只有擁有此物件的鎖的執行緒才能更改通道的阻塞模式。

2.2.1 ServerSocketChannel
讓我們從最簡單的ServerSocketChannel來開始對socket通道類的討論。以下是ServerSocketChannel的完整API:

  public abstract class ServerSocketChannel extends AbstractSelectableChannel
    {
        public static ServerSocketChannel open() throws IOException;
        public abstract ServerSocket socket();
        public abstract ServerSocket accept()throws IOException;
        public final int validOps();
    }  

ServerSocketChannel是一個基於通道的socket監聽器。它同我們所熟悉的java.net.ServerSocket執行相同的基本任務,不過它增加了通道語義,因此能夠在非阻塞模式下執行。

由於ServerSocketChannel沒有bind( )方法,因此有必要取出對等的socket並使用它來繫結到一個埠以開始監聽連線。我們也是使用對等ServerSocket的API來根據需要設定其他的socket選項。

同它的對等體java.net.ServerSocket一樣,ServerSocketChannel也有accept( )方法。一旦您建立了一個ServerSocketChannel並用對等socket綁定了它,然後您就可以在其中一個上呼叫accept( )。如果您選擇在ServerSocket上呼叫accept( )方法,那麼它會同任何其他的ServerSocket表現一樣的行為:總是阻塞並返回一個java.net.Socket物件。如果您選擇在ServerSocketChannel上呼叫accept( )方法則會返回SocketChannel型別的物件,返回的物件能夠在非阻塞模式下執行。

如果以非阻塞模式被呼叫,當沒有傳入連線在等待時,ServerSocketChannel.accept( )會立即返回null。正是這種檢查連線而不阻塞的能力實現了可伸縮性並降低了複雜性。可選擇性也因此得到實現。我們可以使用一個選擇器例項來註冊一個ServerSocketChannel物件以實現新連線到達時自動通知的功能。以下程式碼演示瞭如何使用一個非阻塞的accept( )方法:

 package com.ronsoft.books.nio.channels;
    import java.nio.ByteBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.net.InetSocketAddress;

    public class ChannelAccept
    {
        public static final String GREETING = "Hello I must be going.\r\n";
        public static void main (String [] argv) throws Exception
        {
            int port = 1234; // default
            if (argv.length > 0) {
                port = Integer.parseInt (argv [0]);
            }
            ByteBuffer buffer = ByteBuffer.wrap (GREETING.getBytes());
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.socket().bind (new InetSocketAddress (port));
            ssc.configureBlocking (false);
            while (true) {
                System.out.println ("Waiting for connections");
                SocketChannel sc = ssc.accept(); 
                if (sc == null) {
                    Thread.sleep (2000);
                } else {
                    System.out.println ("Incoming connection from: " + sc.socket().getRemoteSocketAddress());
                    buffer.rewind();
                    sc.write (buffer);
                    sc.close();
                }
            }
        }
    }

2.2.2 SocketChannel

下面開始學習SocketChannel,它是使用最多的socket通道類:

Java NIO中的SocketChannel是一個連線到TCP網路套接字的通道。可以通過以下2種方式建立SocketChannel:

  1. 開啟一個SocketChannel並連線到網際網路上的某臺伺服器。
  2. 一個新連線到達ServerSocketChannel時,會建立一個SocketChannel。

開啟 SocketChannel

下面是SocketChannel的開啟方式:

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

關閉 SocketChannel

當用完SocketChannel之後呼叫SocketChannel.close()關閉SocketChannel:

socketChannel.close();  

從 SocketChannel 讀取資料

要從SocketChannel中讀取資料,呼叫一個read()的方法之一。以下是例子:

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

  首先,分配一個Buffer。從SocketChannel讀取到的資料將會放到這個Buffer中。然後,呼叫SocketChannel.read()。該方法將資料從SocketChannel 讀到Buffer中。read()方法返回的int值表示讀了多少位元組進Buffer裡。如果返回的是-1,表示已經讀到了流的末尾(連線關閉了)。

寫入 SocketChannel

寫資料到SocketChannel用的是SocketChannel.write()方法,該方法以一個Buffer作為引數。示例如下:

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());

buf.flip();

while(buf.hasRemaining()) {
    channel.write(buf);
}

  注意SocketChannel.write()方法的呼叫是在一個while迴圈中的。Write()方法無法保證能寫多少位元組到SocketChannel。所以,我們重複呼叫write()直到Buffer沒有要寫的位元組為止。

非阻塞模式

可以設定 SocketChannel 為非阻塞模式(non-blocking mode).設定之後,就可以在非同步模式下呼叫connect(), read() 和write()了。

connect()

如果SocketChannel在非阻塞模式下,此時呼叫connect(),該方法可能在連線建立之前就返回了。為了確定連線是否建立,可以呼叫finishConnect()的方法。像這樣:

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

while(! socketChannel.finishConnect() ){
    //wait, or do something else...
}

write()

非阻塞模式下,write()方法在尚未寫出任何內容時可能就返回了。所以需要在迴圈中呼叫write()。前面已經有例子了,這裡就不贅述了。

read()

非阻塞模式下,read()方法在尚未讀取到任何資料時可能就返回了。所以需要關注它的int返回值,它會告訴你讀取了多少位元組。

非阻塞模式與選擇器

非阻塞模式與選擇器搭配會工作的更好,通過將一或多個SocketChannel註冊到Selector,可以詢問選擇器哪個通道已經準備好了讀取,寫入等。Selector與SocketChannel的搭配使用會在後面詳講。

2.2.3 DatagramChannel
最後一個socket通道是DatagramChannel。正如SocketChannel對應Socket,ServerSocketChannel對應ServerSocket,每一個DatagramChannel物件也有一個關聯的DatagramSocket物件。不過原命名模式在此並未適用:“DatagramSocketChannel”顯得有點笨拙,因此採用了簡潔的“DatagramChannel”名稱。

正如SocketChannel模擬連線導向的流協議(如TCP/IP),DatagramChannel則模擬包導向的無連線協議(如UDP/IP)。

DatagramChannel是無連線的。每個資料報(datagram)都是一個自包含的實體,擁有它自己的目的地址及不依賴其他資料報的資料負載。與面向流的的socket不同,DatagramChannel可以傳送單獨的資料報給不同的目的地址。同樣,DatagramChannel物件也可以接收來自任意地址的資料包。每個到達的資料報都含有關於它來自何處的資訊(源地址)。

最後給出一個基本的channel例項:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
//讀取的位元組數,可能為零,如果該通道已到達流的末尾,則返回 -1
while (bytesRead != -1) { System.out.println("Read " + bytesRead);
buf.flip();
//反轉緩衝區
while(buf.hasRemaining()){
System.out.print((char) buf.get());
//讀取此緩衝區當前位置的位元組,然後該位置遞增。
} buf.clear();
bytesRead = inChannel.read(buf);
//從緩衝區讀取資料
} aFile.close();

引用連結:感謝文章內容的原創作者;

1.http://www.iteye.com/magazines/132-Java-NIO

2.Java NIO中文版

3.http://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html

4.http://www.yangyong.me/java-nio%E5%85%A5%E9%97%A8%E4%B8%8E%E8%AF%A6%E8%A7%A3/

5.http://ifeve.com/socket-channel/

相關推薦

Java NIO學習筆記---Channel

Java NIO 的核心組成部分: 1.Channels 2.Buffers 3.Selectors   我們首先來學習Channels(java.nio.channels): 通道   1)通道基礎   通道(Channel)是java.nio的第二個主要創新。它們既不是一個擴充套件也不是一項增強,而是全新

Java NIO 學習筆記(一)----概述,Channel/Buffer

Java NIO (來自 Java 1.4)可以替代標準 IO 和 Java Networking API ,NIO 提供了與標準 IO 不同的使用方式。學習 NIO 之前建議先掌握標準 IO 和 Java 網路程式設計,推薦教程: 系統學習 Java IO----目錄,概覽 初步接觸 Java

Java NIO 學習筆記(二)----聚集和分散,通道到通道

目錄: Java NIO 學習筆記(一)----概述,Channel/Buffer Java NIO 學習筆記(二)----聚集和分散,通道到通道 Scatter / Gather 通道的聚集和分散操作 NIO 具有內建的 scatter/gather 支援,用於描述讀取和寫入通道的操作。 分散(

Java NIO 學習筆記(三)----Selector

目錄: Java NIO 學習筆記(一)----概述,Channel/Buffer Java NIO 學習筆記(二)----聚集和分散,通道到通道 Java NIO 學習筆記(三)----Selector 選擇器是一個 NIO 元件,它可以檢測一個或多個 NIO 通道,並確定哪些通道可以用於讀或寫了。 這樣

Java NIO 學習筆記(四)----檔案通道和網路通道

目錄: Java NIO 學習筆記(一)----概述,Channel/Buffer Java NIO 學習筆記(二)----聚集和分散,通道到通道 Java NIO 學習筆記(三)----Selector Java NIO 學習筆記(四)----檔案通道和網路通道 FileChannel 檔案通道 Fil

Java NIO 學習筆記(六)----非同步檔案通道 AsynchronousFileChannel

目錄: Java NIO 學習筆記(一)----概述,Channel/Buffer Java NIO 學習筆記(二)----聚集和分散,通道到通道 Java NIO 學習筆記(三)----Selector Java NIO 學習筆記(四)----檔案通道和網路通道 Java NIO 學習筆記(五)----路徑

Java NIO 學習筆記(五)----路徑、檔案和管道 Path/Files/Pipe

目錄: Java NIO 學習筆記(一)----概述,Channel/Buffer Java NIO 學習筆記(二)----聚集和分散,通道到通道 Java NIO 學習筆記(三)----Selector Java NIO 學習筆記(四)----檔案通道和網路通道 Java NIO 學習筆記(五)----路徑

Java NIO學習筆記:結合原始碼分析+Reactor模式

Java NIO和IO的主要區別 下表總結了Java NIO和IO之間的主要差別,我會更詳細地描述表中每部分的差異。 IO                           NIO 面向流                     面向緩衝 阻塞IO

Java NIO學習筆記---I/O與NIO概述

文章目錄 一、什麼是IO 二、什麼是Java NIO 三、I/O常見概念 3.1 DMA 3.2 核心空間和使用者空間 3.3 虛擬記憶體 3.4 現代作業系統的分頁技術 3.5 面向塊(檔案)的I/O和流I/

Java nio 學習筆記(三)

實現一:使用nio實現檔案複製 package study.nio; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.F

JAVA NIO學習筆記1

最近專案中遇到不少NIO相關知識,之前對這塊接觸得較少,算是我的一個盲區,打算花點時間學習,簡單做一點個人學習總結。 簡介 NIO(New IO)是JDK1.4以後推出的全新IO API,相比傳統IO方式NIO採用了全新的底層I/O模型。傳統IO的設計概念是面向流,而

Java nio 學習筆記(四) 淘寶2012校招技術筆試題

 實現五:統計一個單詞可重複的英文檔案(假設4G)中每個單詞出現的次數,把結果按照英文排序放入一個檔案中。並能夠檢索特定單詞的出現次數。由於檔案過大,不重複單詞總數有限,需要考慮到執行速度和記憶體使用情況。(淘寶筆試技術題) import java.io.File; imp

Java NIO學習筆記三(堆外記憶體之 DirectByteBuffer 詳解)

堆外記憶體 堆外記憶體是相對於堆內記憶體的一個概念。堆內記憶體是由JVM所管控的Java程序記憶體,我們平時在Java中建立的物件都處於堆內記憶體中,並且它們遵循JVM的記憶體管理機制,JVM會採用垃圾回收機制統一管理它們的記憶體。那麼堆外記憶體就是存

Java NIO學習筆記(四) 使用JDK 1.7 NIO2.0 實現客戶端與伺服器的通訊

JDK1.7 提供了全新的非同步NIO模式。稱為:NIO2.0或AIO。該模式引入了新的非同步通道的概念,並提供了非同步檔案通道和非同步套接字通道的實現。非同步通道提供兩種方式獲取獲取操作結果。分別是: 通過java.util.concurrent

Java NIO學習筆記三------Chanel的四種實現篇

FileChannel FileChannel是什麼 FileChannel是一個連線到檔案的通道,可以通過檔案通道讀寫檔案。它無法設定為非阻塞模式,總是執行在阻塞模式下。 開啟FileChannel 我們可以通過使用一個InputStream、OutputStrea

Java NIO 通道(Channel學習筆記

 一、通道(Channel):用於源節點與目標節點的連線。在 Java NIO 中負責緩衝區中資料的傳輸。Channel 本身不儲存資料,因此需要配合緩衝區進行傳輸。    二、通道的主要實現類      java.nio.c

Java NIO學習系列二:Channel

  上文總結了Java NIO中的Buffer相關知識點,本文中我們來總結一下它的好兄弟:Channel。上文有說到,Java NIO中的Buffer一般和Channel配對使用,NIO中的所有IO都起始於一個Channel,一個Channel就相當於一個流,,可以從Channel中讀取資料到Buffer,或

NIO學習筆記,從Linux IO演化模型到Netty—— Java NIO零拷貝

同樣只是大致上的認識。 其中,當使用transferFrom,transferTo的時候用的sendfile()。 如果系統核心不支援 sendfile,進一步執行 transferToTrustedChannel() 方法,以 mmap 的零拷貝方式進行記憶體對映,這種情況下目的通道必須是 FileCh

java NIO 學習

之間 理解 poll 利用 .com 根據 handler react 階段 一、了解Unix網絡編程5種I/O模型 1.1、阻塞式I/O模型 阻塞I/O(blocking I/O)模型,進程調用recvfrom,其系統調用直到數據報到達且被拷貝到應用進程的緩沖區中或者發

java IO 學習筆記

key 網絡 java io writer 讀取 方式 訪問 resources str 1.IO的數據源有: 文件 管道 網絡 內存緩存 讀寫方式有字符讀寫 reader writer ,字節讀寫 Stream。 2.IO的異常處理: try with reso