Java中的I/O流或者輸入/輸出流是指數(shù)據(jù)在本地文件或網(wǎng)絡(luò)中以流的方式進(jìn)行傳輸。新的輸入/輸出(NIO)庫是在JDK1.4版本中引入的。NIO彌補(bǔ)了原來的I/O的不足,它在標(biāo)準(zhǔn)Java代碼中提供了高速的、面向塊的I/O。
原來的I/O庫與NIO最重要的區(qū)別是數(shù)據(jù)打包和傳輸方式的不同,原來的I/O以流的方式處理數(shù)據(jù),而NIO以塊的方式處理數(shù)據(jù)。
面向流的I/O系統(tǒng)一次一個(gè)字節(jié)地處理數(shù)據(jù)。一個(gè)輸入流讀取一個(gè)字節(jié)的數(shù)據(jù),一個(gè)輸出流寫出一個(gè)字節(jié)的數(shù)據(jù),為流式數(shù)據(jù)創(chuàng)建過濾器非常容易。鏈接幾個(gè)過濾器,以便每個(gè)過濾器只負(fù)責(zé)單個(gè)復(fù)雜處理機(jī)制的一部分,這樣也是相對(duì)簡(jiǎn)單的。不利的一面是,面向流的I/O通常相當(dāng)慢。
NIO與原來的I/O有同樣的作用和目的,但是它使用塊I/O的處理方式。每一個(gè)操作都在一步中讀取或者寫出一個(gè)數(shù)據(jù)塊。按塊處理數(shù)據(jù)比按流式的字節(jié)處理數(shù)據(jù)要快很多。但是面向塊的I/O缺少一些面向流的I/O所具有的優(yōu)雅性和簡(jiǎn)單性。
下面我們從一個(gè)簡(jiǎn)單的使用IO和NIO讀取一個(gè)文件中的內(nèi)容為例,進(jìn)行NIO的學(xué)習(xí)。
/**
* 使用IO讀取指定文件的前1024個(gè)字節(jié)的內(nèi)容
* @param file 指定文件名稱
* @throws java.io.IOException IO異常
*/
public void ioRead(String file) throws IOException {
FileInputStream in = new FileInputStream(file);
byte[] b = new byte[1024];
in.read( b );
System.out.println(new String(b));
}
/**
* 使用NIO讀取指定文件的前1024個(gè)字節(jié)的內(nèi)容
* @param file 指定文件名稱
* @throws java.io.IOException IO異常
*/
public void nioRead(Stirng file) thorws IOException {
FileInputStream in = new FileInputStream(file);
FileChannel channel = in.getChanel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read( buffer );
byte[] b = buffer.array();
System.out.println( new String( b ));
} |
通道和緩沖區(qū)是NIO中的核心對(duì)象,幾乎在每一個(gè)I/O操作中都要使用它們。
緩沖區(qū)(Buffer)實(shí)質(zhì)上是一個(gè)容器對(duì)象,它包含一些要寫入或者剛讀出的數(shù)據(jù)。在NIO中加入Buffer對(duì)象,體現(xiàn)了新庫與原I/O的一個(gè)重要區(qū)別。
在面向流的I/O中,將數(shù)據(jù)直接寫入或者將數(shù)據(jù)直接讀到Stream對(duì)象中。
在NIO庫中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。在讀取數(shù)據(jù)時(shí),它是直接讀到緩沖區(qū)中的。在寫入數(shù)據(jù)時(shí),它是寫入到緩沖區(qū)中的。任何時(shí)候訪問NIO中的數(shù)據(jù),都是將它放到緩沖區(qū)中。
緩沖區(qū)實(shí)質(zhì)上是一個(gè)數(shù)組。通常它是一個(gè)字節(jié)數(shù)組,但是也可以使用其他種類的數(shù)組。但是一個(gè)緩沖區(qū)不僅僅是一個(gè)數(shù)組。緩沖區(qū)提供了對(duì)數(shù)據(jù)的結(jié)構(gòu)化訪問,而且還可以跟蹤系統(tǒng)的讀/寫進(jìn)程。最常用的緩沖區(qū)類型是ByteBuffer。 一個(gè)ByteBuffer可以在其底層字節(jié)數(shù)組上進(jìn)行g(shù)et/set操作(即字節(jié)的獲取和設(shè)置)。
通道(Channel)是對(duì)原I/O包中的流的模擬,可以通過它讀取和寫入數(shù)據(jù)。拿NIO與原來的I/O做個(gè)比較,通道就像是流。
正如前面提到的,所有數(shù)據(jù)都通過Buffer對(duì)象來處理。永遠(yuǎn)不會(huì)將字節(jié)直接寫入通道中,相反,而會(huì)將數(shù)據(jù)寫入包含一個(gè)或者多個(gè)字節(jié)的緩沖區(qū)。同樣,不會(huì)直接從通道中讀取字節(jié),而是將數(shù)據(jù)從通道讀入緩沖區(qū),再?gòu)木彌_區(qū)獲取這個(gè)字節(jié)。
通道與流的不同之處在于通道是雙向的。而流只是在一個(gè)方向上移動(dòng)(一個(gè)流必須是
InputStream或者OutputStream的子類), 而通道可以用于讀、寫或者同時(shí)用于讀寫。
讀和寫是I/O的基本過程。從一個(gè)通道中讀取很簡(jiǎn)單:只需創(chuàng)建一個(gè)緩沖區(qū),然后讓通道將數(shù)據(jù)讀到這個(gè)緩沖區(qū)中。寫入也相當(dāng)簡(jiǎn)單:創(chuàng)建一個(gè)緩沖區(qū),用數(shù)據(jù)填充它,然后讓通 道用這些數(shù)據(jù)來執(zhí)行寫入操作。
如果使用原來的I/O,那么我們只需創(chuàng)建一個(gè)FileInputStream并從它那里讀取。而在NIO中,情況稍有不同:我們首先從FileInputStream獲取一個(gè)FileChannel對(duì)象,然后使用這個(gè)通道來讀取數(shù)據(jù)。
在NIO系統(tǒng)中,任何時(shí)候執(zhí)行一個(gè)讀操作,都是從通道中讀取,但是不是直接從通道讀取。因?yàn)樗袛?shù)據(jù)最終都駐留在緩沖區(qū)中,所以是從通道讀到緩沖區(qū)中。
現(xiàn)在,讓我們看一下NIO基本讀寫數(shù)據(jù)的過程。
在NIO中讀取文件涉及的三個(gè)步驟:
// 第一步是獲取通道。我們從 FileInputStream 獲取通道:
FileInputStream fin = new FileInputStream( "readandshow.txt" );
FileChannel fc = fin.getChannel();
// 下一步是創(chuàng)建緩沖區(qū):
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
// 最后,需要將數(shù)據(jù)從通道讀到緩沖區(qū)中:
fc.read( buffer ); |
在 NIO 中寫入文件類似于從文件中讀取
// 首先從 FileOutputStream 獲取一個(gè)通道:
FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );
FileChannel fc = fout.getChannel();
// 下一步是創(chuàng)建一個(gè)緩沖區(qū)并在其中放入一些數(shù)據(jù),這里,用data來表示一個(gè)持有數(shù)據(jù)的數(shù)組。
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
for (int i=0; i<data.length; ++i) {
buffer.put( data[i] );
}
buffer.flip();
// 最后一步是寫入緩沖區(qū)中:
fc.write( buffer ); |
下面使用NIO進(jìn)行讀寫結(jié)合,將一個(gè)文件的所有內(nèi)容拷貝到另一個(gè)文件中
/**
* 將一個(gè)文件的所有內(nèi)容拷貝到另一個(gè)文件中。
* 執(zhí)行三個(gè)基本操作:
* 首先創(chuàng)建一個(gè) Buffer
* 然后從源文件中將數(shù)據(jù)讀到這個(gè)緩沖區(qū)中
* 最后將緩沖區(qū)寫入目標(biāo)文件
* 程序不斷重復(fù)(讀、寫、讀、寫) 直到源文件結(jié)束
*/
public static void main(String[] args) throws Exception {
String infile = "C:\\copy.sql";String outfile = "C:\\copy.txt";
// 獲取源文件和目標(biāo)文件的輸入輸出流
FileInputStream fin = new FileInputStream(infile);
FileOutputStream fout = new FileOutputStream(outfile);
// 獲取輸入輸出通道
FileChannel fcin = fin.getChannel();
FileChannel fcout = fout.getChannel();
// 創(chuàng)建緩沖區(qū)
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
// clear方法,重設(shè)緩沖區(qū),使它可以接受讀入的數(shù)據(jù)
buffer.clear();
// 從輸入通道中將數(shù)據(jù)讀到緩沖區(qū)
int r = fcin.read(buffer);
// read方法,返回讀取的字節(jié)數(shù),可能為零,如果該通道已到達(dá)流的末尾則返回-1
if (r == -1) {
break;
}
// flip方法,讓緩沖區(qū)可以將新讀入的數(shù)據(jù),寫入到另一個(gè)通道中
buffer.flip();
// 從輸出通道中將數(shù)據(jù)寫入緩沖區(qū)
fcout.write(buffer);
}
} |
通過以上的介紹,我們懂得了什么是NIO,了解了NIO與IO的區(qū)別,以及如何通過NIO進(jìn)行文件的基本讀寫操作。若您對(duì)NIO感興趣,還可以進(jìn)行更深入的學(xué)習(xí)了解。