Channel是一個通道刮吧,可以通過它讀取和寫入數(shù)據(jù)打瘪,它就像自來水管一樣,網(wǎng)絡(luò)數(shù)據(jù)通過Channel讀取和寫入奋姿。通道與流的不同之處在于通道是雙向的,流只是在一個方向上移動(一個流必須是InputStream或者OutputStream的子類)素标,而且通道可以用于讀称诗、寫或者同事用于讀寫。因為Channel是全雙工的头遭,所以它可以比流更好地映射底層操作系統(tǒng)的API寓免。特別是在UNIX網(wǎng)絡(luò)編程模型中,底層操作系統(tǒng)的通道都是全雙工的计维,同時支持讀寫操作袜香。
NIO中通過channel封裝了對數(shù)據(jù)源的操作,通過channel 我們可以操作數(shù)據(jù)源鲫惶,但又不必關(guān)心數(shù)據(jù)源的具體物理結(jié)構(gòu)蜈首。
這個數(shù)據(jù)源可能是多種的。比如欠母,可以是文件欢策,也可以是網(wǎng)絡(luò)socket。在大多數(shù)應(yīng)用中赏淌,channel與文件描述符或者socket是一一對應(yīng)的踩寇。Channel用于在字節(jié)緩沖區(qū)和位于通道另一側(cè)的實體(通常是一個文件或套接字)之間有效地傳輸數(shù)據(jù)。
channel接口源碼:
package java.nio.channels;publicinterface Channel;
{
? ? publicboolean isOpen();
? ? publicvoidclose()throws IOException;
}
與緩沖區(qū)不同六水,通道API主要由接口指定姑荷。不同的操作系統(tǒng)上通道實現(xiàn)(Channel Implementation)會有根本性的差異,所以通道API僅僅描述了可以做什么缩擂。因此很自然地鼠冕,通道實現(xiàn)經(jīng)常使用操作系統(tǒng)的本地代碼。通道接口允許您以一種受控且可移植的方式來訪問底層的I/O服務(wù)胯盯。
Channel是一個對象懈费,可以通過它讀取和寫入數(shù)據(jù)。拿 NIO 與原來的 I/O 做個比較博脑,通道就像是流憎乙。所有數(shù)據(jù)都通過Buffer對象來處理。您永遠不會將字節(jié)直接寫入通道中叉趣,相反泞边,您是將數(shù)據(jù)寫入包含一個或者多個字節(jié)的緩沖區(qū)。同樣疗杉,您不會直接從通道中讀取字節(jié)氮双,而是將數(shù)據(jù)從通道讀入緩沖區(qū),再從緩沖區(qū)獲取這個字節(jié)宴抚。
Java NIO的通道類似流,但又有些不同:
既可以從通道中讀取數(shù)據(jù)奠蹬,又可以寫數(shù)據(jù)到通道。但流的讀寫通常是單向的嗡午。
通道可以異步地讀寫囤躁。
通道中的數(shù)據(jù)總是要先讀到一個Buffer,或者總是要從一個Buffer中寫入荔睹。
正如上面所說狸演,從通道讀取數(shù)據(jù)到緩沖區(qū),從緩沖區(qū)寫入數(shù)據(jù)到通道僻他。如下圖所示:
Channel的實現(xiàn)
這些是Java NIO中最重要的通道的實現(xiàn):
FileChannel:從文件中讀寫數(shù)據(jù)
DatagramChannel:通過UDP讀寫網(wǎng)絡(luò)中的數(shù)據(jù)
SocketChannel:通過TCP讀寫網(wǎng)絡(luò)中的數(shù)據(jù)
ServerSocketChannel:可以監(jiān)聽新進來的TCP連接宵距,像Web服務(wù)器那樣。對每一個新進來的連接都會創(chuàng)建一個SocketChannel中姜。
正如你所看到的,這些通道涵蓋了UDP 和 TCP 網(wǎng)絡(luò)IO跟伏,以及文件IO丢胚。
FileChannel
FileChannel類可以實現(xiàn)常用的read,write以及scatter/gather操作受扳,同時它也提供了很多專用于文件的新方法携龟。這些方法中的許多都是我們所熟悉的文件操作。
FileChannel類的JDK源碼:
package java.nio.channels;
? ? publicabstractclassFileChannelextendsAbstractChannelimplements ByteChannel, GatheringByteChannel, ScatteringByteChannel
? ? {
? ? ? ? // This is a partial API listing
? ? ? ? // All methods listed here can throw java.io.IOExceptionpublicabstractintread (ByteBuffer dst,long position);
? ? ? ? publicabstractintwrite (ByteBuffer src,long position);
? ? ? ? publicabstractlong size();
? ? ? ? publicabstractlong position();
? ? ? ? publicabstractvoidposition (long newPosition);
? ? ? ? publicabstractvoidtruncate (long size);
? ? ? ? publicabstractvoidforce (boolean metaData);
? ? ? ? publicfinal FileLock lock();
? ? ? ? publicabstractFileLock lock (longposition,longsize,boolean shared);
? ? ? ? publicfinal FileLock tryLock();
? ? ? ? publicabstractFileLock tryLock (longposition,longsize,boolean shared);
? ? ? ? publicabstractMappedByteBuffer map (MapMode mode,longposition,long size);
? ? ? ? publicstaticclass MapMode;
? ? ? ? publicstaticfinal MapMode READ_ONLY;
? ? ? ? publicstaticfinal MapMode READ_WRITE;
? ? ? ? publicstaticfinal MapMode PRIVATE;
? ? ? ? publicabstractlongtransferTo (longposition,long count, WritableByteChannel target);
? ? ? ? publicabstractlongtransferFrom (ReadableByteChannel src,longposition,long count);
? ? }
文件通道總是阻塞式的勘高,因此不能被置于非阻塞模式∠矿現(xiàn)代操作系統(tǒng)都有復(fù)雜的緩存和預(yù)取機制,使得本地磁盤I/O操作延遲很少华望。網(wǎng)絡(luò)文件系統(tǒng)一般而言延遲會多些蕊蝗,不過卻也因該優(yōu)化而受益。面向流的I/O的非阻塞范例對于面向文件的操作并無多大意義赖舟,這是由文件I/O本質(zhì)上的不同性質(zhì)造成的蓬戚。對于文件I/O,最強大之處在于異步I/O(asynchronous I/O)宾抓,它允許一個進程可以從操作系統(tǒng)請求一個或多個I/O操作而不必等待這些操作的完成子漩。發(fā)起請求的進程之后會收到它請求的I/O操作已完成的通知。
FileChannel對象是線程安全(thread-safe)的石洗。多個進程可以在同一個實例上并發(fā)調(diào)用方法而不會引起任何問題幢泼,不過并非所有的操作都是多線程的(multithreaded)。影響通道位置或者影響文件大小的操作都是單線程的(single-threaded)讲衫。如果有一個線程已經(jīng)在執(zhí)行會影響通道位置或文件大小的操作缕棵,那么其他嘗試進行此類操作之一的線程必須等待。并發(fā)行為也會受到底層的操作系統(tǒng)或文件系統(tǒng)影響。
每個FileChannel對象都同一個文件描述符(file descriptor)有一對一的關(guān)系挥吵,所以上面列出的API方法與在您最喜歡的POSIX(可移植操作系統(tǒng)接口)兼容的操作系統(tǒng)上的常用文件I/O系統(tǒng)調(diào)用緊密對應(yīng)也就不足為怪了重父。本質(zhì)上講,RandomAccessFile類提供的是同樣的抽象內(nèi)容忽匈。在通道出現(xiàn)之前房午,底層的文件操作都是通過RandomAccessFile類的方法來實現(xiàn)的。FileChannel模擬同樣的I/O服務(wù)丹允,因此它的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( )
下面是一個使用FileChannel讀取數(shù)據(jù)到Buffer中的示例:
package com.dxz.springsession.nio.demo1;import java.io.IOException;import java.io.RandomAccessFile;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;publicclass FileChannelTest {
? ? /**? ? * @param args
? ? * @throws IOException
? ? */publicstaticvoidmain(String[] args)throws IOException {
? ? ? ? RandomAccessFile aFile =newRandomAccessFile("d:\\soft\\nio-data.txt", "rw");
? ? ? ? FileChannel inChannel = aFile.getChannel();
? ? ? ? ByteBuffer buf = ByteBuffer.allocate(48);
? ? ? ? intbytesRead = inChannel.read(buf);
? ? ? ? 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();
? ? ? ? System.out.println("wan");
? ? }
}
文件內(nèi)容:
1234567qwertrewq
uytrewq
hgfdsa
nbvcxz
iop89
輸出結(jié)果:
Read 481234567qwertrewq
uytrewq
hgfdsa
nbvcxz
iop89wan
注意 buf.flip() 的調(diào)用,首先讀取數(shù)據(jù)到Buffer雕蔽,然后反轉(zhuǎn)Buffer,接著再從Buffer中讀取數(shù)據(jù)折柠。下一節(jié)會深入講解Buffer的更多細節(jié)。
1批狐、打開FileChannel
在使用FileChannel之前扇售,必須先打開它。但是嚣艇,我們無法直接打開一個FileChannel承冰,需要通過使用一個InputStream、OutputStream或RandomAccessFile來獲取一個FileChannel實例食零。下面是通過RandomAccessFile打開FileChannel的示例:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
2困乒、從FileChannel讀取數(shù)據(jù)
調(diào)用多個read()方法之一從FileChannel中讀取數(shù)據(jù)。如:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
首先贰谣,分配一個Buffer娜搂。從FileChannel中讀取的數(shù)據(jù)將被讀到Buffer中。
然后吱抚,調(diào)用FileChannel.read()方法百宇。該方法將數(shù)據(jù)從FileChannel讀取到Buffer中。read()方法返回的int值表示了有多少字節(jié)被讀到了Buffer中秘豹。如果返回-1恳谎,表示到了文件末尾。
3憋肖、向FileChannel寫數(shù)據(jù)
使用FileChannel.write()方法向FileChannel寫數(shù)據(jù)因痛,該方法的參數(shù)是一個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);
}
注意FileChannel.write()是在while循環(huán)中調(diào)用的岸更。因為無法保證write()方法一次能向FileChannel寫入多少字節(jié)鸵膏,因此需要重復(fù)調(diào)用write()方法,直到Buffer中已經(jīng)沒有尚未寫入通道的字節(jié)怎炊。
4谭企、關(guān)閉FileChannel
用完FileChannel后必須將其關(guān)閉廓译。如:
channel.close();
5、FileChannel的position方法
有時可能需要在FileChannel的某個特定位置進行數(shù)據(jù)的讀/寫操作债查》乔可以通過調(diào)用position()方法獲取FileChannel的當(dāng)前位置。
也可以通過調(diào)用position(long pos)方法設(shè)置FileChannel的當(dāng)前位置盹廷。
這里有兩個例子:
long pos = channel.position();
channel.position(pos +123);
如果將位置設(shè)置在文件結(jié)束符之后征绸,然后試圖從文件通道中讀取數(shù)據(jù),讀方法將返回-1 —— 文件結(jié)束標(biāo)志俄占。
如果將位置設(shè)置在文件結(jié)束符之后管怠,然后向通道中寫數(shù)據(jù),文件將撐大到當(dāng)前位置并寫入數(shù)據(jù)缸榄。這可能導(dǎo)致“文件空洞”渤弛,磁盤上物理文件中寫入的數(shù)據(jù)間有空隙。
6甚带、FileChannel的size方法
FileChannel實例的size()方法將返回該實例所關(guān)聯(lián)文件的大小她肯。如:
long fileSize = channel.size();
7、FileChannel的truncate方法
可以使用FileChannel.truncate()方法截取一個文件鹰贵。截取文件時晴氨,文件將中指定長度后面的部分將被刪除。如:
channel.truncate(1024);
這個例子截取文件的前1024個字節(jié)砾莱。
8瑞筐、FileChannel的force方法
FileChannel.force()方法將通道里尚未寫入磁盤的數(shù)據(jù)強制寫到磁盤上凄鼻。出于性能方面的考慮腊瑟,操作系統(tǒng)會將數(shù)據(jù)緩存在內(nèi)存中,所以無法保證寫入到FileChannel里的數(shù)據(jù)一定會即時寫到磁盤上块蚌。要保證這一點闰非,需要調(diào)用force()方法。
force()方法有一個boolean類型的參數(shù)峭范,指明是否同時將文件元數(shù)據(jù)(權(quán)限信息等)寫到磁盤上财松。
下面的例子同時將文件數(shù)據(jù)和元數(shù)據(jù)強制寫到磁盤上:
channel.force(true);
示例:
package com.dxz.nio;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelRead {
? ? static public void main(String args[]) throws Exception {
? ? ? ? FileInputStream fin = new FileInputStream("e:\\logs\\test.txt");
? ? ? ? // 獲取通道
? ? ? ? FileChannel fc = fin.getChannel();
? ? ? ? // 創(chuàng)建緩沖區(qū)
? ? ? ? ByteBuffer buffer = ByteBuffer.allocate(1024);
? ? ? ? // 讀取數(shù)據(jù)到緩沖區(qū)
? ? ? ? fc.read(buffer);
? ? ? ? buffer.flip();
? ? ? ? while (buffer.remaining() > 0) {
? ? ? ? ? ? byte b = buffer.get();
? ? ? ? ? ? System.out.print(((char) b));
? ? ? ? }
? ? ? ? fin.close();
? ? }
}
寫入:
package com.dxz.nio;import java.io.FileOutputStream;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;publicclass FileChannelWrite {
? ? staticprivatefinalbytemessage[] = { 83, 111, 109, 101, 32, 98, 121, 116, 101, 115, 46 };
? ? staticpublicvoidmain(String args[])throws Exception {
? ? ? ? FileOutputStream fout =newFileOutputStream("e:\\logs\\test2.txt");
? ? ? ? FileChannel fc = fout.getChannel();
? ? ? ? ByteBuffer buffer = ByteBuffer.allocate(1024);
? ? ? ? for(inti = 0; i < message.length; ++i) {
? ? ? ? ? ? buffer.put(message[i]);
? ? ? ? }
? ? ? ? buffer.flip();
? ? ? ? fc.write(buffer);
? ? ? ? fout.close();
? ? }
}
9、FileChannel的transferTo和transferFrom方法--通道之間的數(shù)據(jù)傳輸
如果兩個通道中有一個是FileChannel纱控,那你可以直接將數(shù)據(jù)從一個channel(譯者注:channel中文常譯作通道)傳輸?shù)搅硗庖粋€channel辆毡。
transferFrom()
FileChannel的transferFrom()方法可以將數(shù)據(jù)從源通道傳輸?shù)紽ileChannel中(譯者注:這個方法在JDK文檔中的解釋為將字節(jié)從給定的可讀取字節(jié)通道傳輸?shù)酱送ǖ赖奈募校O旅媸且粋€簡單的例子:
通過FileChannel完成文件間的拷貝:
package com.dxz.springsession.nio.demo1;import java.io.IOException;import java.io.RandomAccessFile;import java.nio.channels.FileChannel;publicclass FileChannelTest2 {
? ? publicstaticvoidmain(String[] args)throws IOException {
? ? ? ? RandomAccessFile aFile =newRandomAccessFile("d:\\soft\\fromFile.txt", "rw");
? ? ? ? FileChannel fromChannel = aFile.getChannel();
? ? ? ? RandomAccessFile bFile =newRandomAccessFile("d:\\soft\\toFile.txt", "rw");
? ? ? ? FileChannel toChannel = bFile.getChannel();
? ? ? ? longposition = 0;
? ? ? ? longcount = fromChannel.size();
? ? ? ? toChannel.transferFrom(fromChannel, position, count);
? ? ? ? aFile.close();
? ? ? ? bFile.close();
? ? ? ? System.out.println("over!");
? ? }
}
方法的輸入?yún)?shù)position表示從position處開始向目標(biāo)文件寫入數(shù)據(jù)甜害,count表示最多傳輸?shù)淖止?jié)數(shù)舶掖。如果源通道的剩余空間小于 count 個字節(jié),則所傳輸?shù)淖止?jié)數(shù)要小于請求的字節(jié)數(shù)尔店。
此外要注意眨攘,在SoketChannel的實現(xiàn)中主慰,SocketChannel只會傳輸此刻準(zhǔn)備好的數(shù)據(jù)(可能不足count字節(jié))。因此鲫售,SocketChannel可能不會將請求的所有數(shù)據(jù)(count個字節(jié))全部傳輸?shù)紽ileChannel中共螺。
transferTo()
transferTo()方法將數(shù)據(jù)從FileChannel傳輸?shù)狡渌腸hannel中。下面是一個簡單的例子:
package com.dxz.springsession.nio.demo1;import java.io.IOException;import java.io.RandomAccessFile;import java.nio.channels.FileChannel;publicclass FileChannelTest3 {
? ? publicstaticvoidmain(String[] args)throws IOException {
? ? ? ? RandomAccessFile aFile =newRandomAccessFile("d:\\soft\\fromFile.txt", "rw");
? ? ? ? FileChannel fromChannel = aFile.getChannel();
? ? ? ? RandomAccessFile bFile =newRandomAccessFile("d:\\soft\\toFile.txt", "rw");
? ? ? ? FileChannel toChannel = bFile.getChannel();
? ? ? ? longposition = 0;
? ? ? ? longcount = fromChannel.size();
? ? ? ? fromChannel.transferTo(position, count, toChannel);
? ? ? ? aFile.close();
? ? ? ? bFile.close();
? ? ? ? System.out.println("over!");
? ? }
}
是不是發(fā)現(xiàn)這個例子和前面那個例子特別相似情竹?除了調(diào)用方法的FileChannel對象不一樣外藐不,其他的都一樣。
上面所說的關(guān)于SocketChannel的問題在transferTo()方法中同樣存在鲤妥。SocketChannel會一直傳輸數(shù)據(jù)直到目標(biāo)buffer被填滿佳吞。