NIO(1)

一、基本概念描述

1.1 I/O簡介

I/O即輸入輸出,是計(jì)算機(jī)與外界世界的一個(gè)借口。IO操作的實(shí)際主題是操作系統(tǒng)。在java編程中,一般使用流的方式來處理IO,所有的IO都被視作是單個(gè)字節(jié)的移動,通過stream對象一次移動一個(gè)字節(jié)。流IO負(fù)責(zé)把對象轉(zhuǎn)換為字節(jié),然后再轉(zhuǎn)換為對象。

1.2 什么是NIO

NIO即New IO,這個(gè)庫是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但實(shí)現(xiàn)方式不同,NIO主要用到的是塊,所以NIO的效率要比IO高很多。

在Java API中提供了兩套NIO,一套是針對標(biāo)準(zhǔn)輸入輸出NIO,另一套就是網(wǎng)絡(luò)編程N(yùn)IO

1.3 流與塊的比較

NIO和IO最大的區(qū)別是數(shù)據(jù)打包和傳輸方式。IO是以的方式處理數(shù)據(jù),而NIO是以的方式處理數(shù)據(jù)。

面向流的IO一次一個(gè)字節(jié)的處理數(shù)據(jù),一個(gè)輸入流產(chǎn)生一個(gè)字節(jié),一個(gè)輸出流就消費(fèi)一個(gè)字節(jié)。為流式數(shù)據(jù)創(chuàng)建過濾器就變得非常容易,鏈接幾個(gè)過濾器,以便對數(shù)據(jù)進(jìn)行處理非常方便而簡單,但是面向流的IO通常處理的很慢。

面向塊的IO系統(tǒng)以塊的形式處理數(shù)據(jù)。每一個(gè)操作都在一步中產(chǎn)生或消費(fèi)一個(gè)數(shù)據(jù)塊。按塊要比按流快的多,但面向塊的IO缺少了面向流IO所具有的有雅興和簡單性。

二、NIO基礎(chǔ)

BufferChannel是標(biāo)準(zhǔn)NIO中的核心對象(網(wǎng)絡(luò)NIO中還有個(gè)Selector核心對象),幾乎每一個(gè)IO操作中都會用到它們。

Channel是對原IO中流的模擬,任何來源和目的數(shù)據(jù)都必須通過一個(gè)Channel對象。一個(gè)Buffer實(shí)質(zhì)上是一個(gè)容器對象,發(fā)給Channel的所有對象都必須先放到Buffer中;同樣的,從Channel中讀取的任何數(shù)據(jù)都要讀到Buffer中。

2.1 關(guān)于Buffer

Buffer是一個(gè)對象,它包含一些要寫入或讀出的數(shù)據(jù)。在NIO中,數(shù)據(jù)是放入buffer對象的,而在IO中,數(shù)據(jù)是直接寫入或者讀到Stream對象的。應(yīng)用程序不能直接對 Channel 進(jìn)行讀寫操作,而必須通過 Buffer 來進(jìn)行,即 Channel 是通過 Buffer 來讀寫數(shù)據(jù)的。

在NIO中,所有的數(shù)據(jù)都是用Buffer處理的,它是NIO讀寫數(shù)據(jù)的中轉(zhuǎn)池。Buffer實(shí)質(zhì)上是一個(gè)數(shù)組,通常是一個(gè)字節(jié)數(shù)據(jù),但也可以是其他類型的數(shù)組。但一個(gè)緩沖區(qū)不僅僅是一個(gè)數(shù)組,重要的是它提供了對數(shù)據(jù)的結(jié)構(gòu)化訪問,而且還可以跟蹤系統(tǒng)的讀寫進(jìn)程。

使用 Buffer 讀寫數(shù)據(jù)一般遵循以下四個(gè)步驟:

????1.寫入數(shù)據(jù)到 Buffer;

????2.調(diào)用 flip() 方法;

????3.從 Buffer 中讀取數(shù)據(jù);

????4.調(diào)用 clear() 方法或者 compact() 方法。

當(dāng)向 Buffer 寫入數(shù)據(jù)時(shí),Buffer 會記錄下寫了多少數(shù)據(jù)。一旦要讀取數(shù)據(jù),需要通過 flip() 方法將 Buffer?從寫模式切換到讀模式。在讀模式下,可以讀取之前寫入到 Buffer 的所有數(shù)據(jù)。

一旦讀完了所有的數(shù)據(jù),就需要清空緩沖區(qū),讓它可以再次被寫入。有兩種方式能清空緩沖區(qū):調(diào)用 clear() 或 compact() 方法。clear() 方法會清空整個(gè)緩沖區(qū)。compact() 方法只會清除已經(jīng)讀過的數(shù)據(jù)。任何未讀的數(shù)據(jù)都被移到緩沖區(qū)的起始處,新寫入的數(shù)據(jù)將放到緩沖區(qū)未讀數(shù)據(jù)的后面。

Buffer主要有如下幾種:


2.3 關(guān)于Channel

Channel是一個(gè)對象,可以通過它讀取和寫入數(shù)據(jù)??梢园阉醋鯥O中的流。但是它和流相比還有一些不同:

Channel是雙向的,既可以讀又可以寫,而流是單向的

Channel可以進(jìn)行異步的讀寫

對Channel的讀寫必須通過buffer對象

正如上面提到的,所有數(shù)據(jù)都通過Buffer對象處理,所以,您永遠(yuǎn)不會將字節(jié)直接寫入到Channel中,相反,您是將數(shù)據(jù)寫入到Buffer中;同樣,您也不會從Channel中讀取字節(jié),而是將數(shù)據(jù)從Channel讀入Buffer,再從Buffer獲取這個(gè)字節(jié)。

因?yàn)镃hannel是雙向的,所以Channel可以比流更好地反映出底層操作系統(tǒng)的真實(shí)情況。特別是在Unix模型中,底層操作系統(tǒng)通常都是雙向的。

在Java NIO中Channel主要有如下幾種類型:

FileChannel:從文件讀取數(shù)據(jù)的

DatagramChannel:讀寫UDP網(wǎng)絡(luò)協(xié)議數(shù)據(jù)

SocketChannel:讀寫TCP網(wǎng)絡(luò)協(xié)議數(shù)據(jù)

ServerSocketChannel:可以監(jiān)聽TCP連接

三、從理論到實(shí)踐:NIO中的讀和寫

IO中的讀和寫,對應(yīng)的是數(shù)據(jù)和Stream,NIO中的讀和寫,則對應(yīng)的就是通道和緩沖區(qū)。NIO中從通道中讀?。簞?chuàng)建一個(gè)緩沖區(qū),然后讓通道讀取數(shù)據(jù)到緩沖區(qū)。NIO寫入數(shù)據(jù)到通道:創(chuàng)建一個(gè)緩沖區(qū),用數(shù)據(jù)填充它,然后讓通道用這些數(shù)據(jù)來執(zhí)行寫入。

3.1 從文件中讀取

我們已經(jīng)知道,在NIO系統(tǒng)中,任何時(shí)候執(zhí)行一個(gè)讀操作,您都是從Channel中讀取,而您不是直接從Channel中讀取數(shù)據(jù),因?yàn)樗械臄?shù)據(jù)都必須用Buffer來封裝,所以您應(yīng)該是從Channel讀取數(shù)據(jù)到Buffer。

因此,如果從文件讀取數(shù)據(jù)的話,需要如下三步:

從FileInputStream獲取Channel

創(chuàng)建Buffer

從Channel讀取數(shù)據(jù)到Buffer

下面我們看一下具體過程:?

第一步:獲取通道

FileInputStream fin = new FileInputStream( "readandshow.txt" );

FileChannel fc = fin.getChannel();?


第二步:創(chuàng)建緩沖區(qū)

ByteBuffer buffer = ByteBuffer.allocate( 1024 );


第三步:將數(shù)據(jù)從通道讀到緩沖區(qū)

fc.read( buffer );


3.2 寫入數(shù)據(jù)到文件

類似于從文件讀數(shù)據(jù),?

第一步:獲取一個(gè)通道

FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );

FileChannel fc = fout.getChannel();


第二步:創(chuàng)建緩沖區(qū),將數(shù)據(jù)放入緩沖區(qū)

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

for (int i=0; i

buffer.put( message[i] );

}

buffer.flip();


第三步:把緩沖區(qū)數(shù)據(jù)寫入通道中

fc.write( buffer );


3.3 讀寫結(jié)合

CopyFile是一個(gè)非常好的讀寫結(jié)合的例子,我們將通過CopyFile這個(gè)實(shí)力讓大家體會NIO的操作過程。CopyFile執(zhí)行三個(gè)基本的操作:創(chuàng)建一個(gè)Buffer,然后從源文件讀取數(shù)據(jù)到緩沖區(qū),然后再將緩沖區(qū)寫入目標(biāo)文件。

/**

* 用java NIO api拷貝文件

* @param src

* @param dst

* @throws IOException

*/

public static void copyFileUseNIO(String src,String dst) throws IOException{

? ? //聲明源文件和目標(biāo)文件

? ? ? ? ? ? FileInputStream fi=new FileInputStream(new File(src));

? ? ? ? ? ? FileOutputStream fo=new FileOutputStream(new File(dst));

? ? ? ? ? ? //獲得傳輸通道channel

? ? ? ? ? ? FileChannel inChannel=fi.getChannel();

? ? ? ? ? ? FileChannel outChannel=fo.getChannel();

? ? ? ? ? ? //獲得容器buffer

? ? ? ? ? ? ByteBuffer buffer=ByteBuffer.allocate(1024);

? ? ? ? ? ? while(true){

? ? ? ? ? ? ? ? //判斷是否讀完文件

? ? ? ? ? ? ? ? int eof =inChannel.read(buffer);

? ? ? ? ? ? ? ? if(eof==-1){

? ? ? ? ? ? ? ? ? ? break;?

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? //重設(shè)一下buffer的position=0,limit=position

? ? ? ? ? ? ? ? buffer.flip();

? ? ? ? ? ? ? ? //開始寫

? ? ? ? ? ? ? ? outChannel.write(buffer);

? ? ? ? ? ? ? ? //寫完要重置buffer,重設(shè)position=0,limit=capacity

? ? ? ? ? ? ? ? buffer.clear();

? ? ? ? ? ? }

? ? ? ? ? ? inChannel.close();

? ? ? ? ? ? outChannel.close();

? ? ? ? ? ? fi.close();

? ? ? ? ? ? fo.close();

}? ?


四、需要注意的點(diǎn)

上面程序中有三個(gè)地方需要注意

4.1 檢查狀態(tài)

當(dāng)沒有更多的數(shù)據(jù)時(shí),拷貝就算完成,此時(shí) read() 方法會返回 -1 ,我們可以根據(jù)這個(gè)方法判斷是否讀完。

int r= fcin.read( buffer );

if (r==-1) {

? ? break;

? ? }


4.2 Buffer類的flip、clear方法

控制buffer狀態(tài)的三個(gè)變量

position:跟蹤已經(jīng)寫了多少數(shù)據(jù)或讀了多少數(shù)據(jù),它指向的是下一個(gè)字節(jié)來自哪個(gè)位置

limit:代表還有多少數(shù)據(jù)可以取出或還有多少空間可以寫入,它的值小于等于capacity。

capacity:代表緩沖區(qū)的最大容量,一般新建一個(gè)緩沖區(qū)的時(shí)候,limit的值和capacity的值默認(rèn)是相等的。

flip、clear這兩個(gè)方法便是用來設(shè)置這些值的。

flip方法

我們先看一下flip的源碼:

public final Buffer flip() {

? ? limit = position;

? ? position = 0;

? ? mark = -1;

? ? return this;

}


在上面的FileCopy程序中,寫入數(shù)據(jù)之前我們調(diào)用了buffer.flip();方法,這個(gè)方法把當(dāng)前的指針位置position設(shè)置成了limit,再將當(dāng)前指針position指向數(shù)據(jù)的最開始端,我們現(xiàn)在可以將數(shù)據(jù)從緩沖區(qū)寫入通道了。 position 被設(shè)置為 0,這意味著我們得到的下一個(gè)字節(jié)是第一個(gè)字節(jié)。 limit 已被設(shè)置為原來的 position,這意味著它包括以前讀到的所有字節(jié),并且一個(gè)字節(jié)也不多。

clear方法

先看一下clear的源碼:

public final Buffer clear() {

? ? position = 0;

? ? limit = capacity;

? ? mark = -1;

? ? return this;

}


在上面的FileCopy程序中,寫入數(shù)據(jù)之后也就是讀數(shù)據(jù)之前,我們調(diào)用了?buffer.clear();方法,這個(gè)方法重設(shè)緩沖區(qū)以便接收更多的字節(jié)。上圖顯示了在調(diào)用 clear() 后緩沖區(qū)的狀態(tài)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Java-NIO(1) 概述 Java NIO(New IO)是一個(gè)可以替代標(biāo)準(zhǔn)Java IO API的IO AP...
    Cool_Pomelo閱讀 1,014評論 0 15
  • 意義 一個(gè)Buffer對象是固定數(shù)量的數(shù)據(jù)的容器,實(shí)質(zhì)上是一個(gè)數(shù)組,其作用是一個(gè)存儲器,或者分段運(yùn)輸區(qū),數(shù)據(jù)可被存...
    赤子心_d709閱讀 549評論 0 0
  • 什么是JavaNIO NIO全名(NewInput/ Output),在java1.4中引入。NIO是一種同步...
    super_wing閱讀 348評論 0 0
  • Java NIO 由以下幾個(gè)核心部分組成: Channels、Buffers、Selectors ...
    未名枯草閱讀 665評論 0 4
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂有人憂愁,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,898評論 28 54

友情鏈接更多精彩內(nèi)容