背景 組成部分 通道和緩衝器 讀寫執行個體
背景
JDK1.4的java.nio.*包中引入了新的JavaI/O類庫,其目的在於 提高速度。實際上,舊的I/O包已經使用nio重新實現過。因此,即使不顯式使用nio編寫代碼,也能從中受益。
I/O的應用情境分為檔案I/O和網路I/O,在這裡之研究前者。 組成部分
nio主要有三大組成部分:通道(Channel)、緩衝器(Buffer)、Selector(選擇區)。
因為nio的資料轉送結構更接近於作業系統執行I/O的方式:通道和緩衝器,而傳統的I/O是面向位元組流和字元流,所以nio的速度更快。
通道用於存放資料,緩衝器用於傳輸資料,選擇區用於監聽多個通道的事件(比如:串連開啟,資料到達)。 通道和緩衝器
我們並沒有直接和通道互動,我們只是和緩衝器互動,並把緩衝器派送到通道。通道要麼從緩衝器獲得資料,要麼向緩衝器發送資料。
唯一直接與通道互動的緩衝器是ByteBuffer,即可以儲存未加工位元組的緩衝器。它是一個很基礎的類:通過告知分配多少儲存空間來建立一個ByteBuffer對象,並且還有一個選擇集。用於以原始的位元組形式或基礎資料型別 (Elementary Data Type)輸出和讀取資料。但是它不能直接讀取或輸出對象,即使是字串對象也不行。雖然這種處理很低級,但是卻更貼合作業系統的處理方式。
舊I/O類庫中的FileInputStream、FileOutputStream、RandomAccessFile這三個類被修改了,用以產生FileChannel。這三個類都是位元組操縱流,與nio性質一致。像Reader和Writer這種面向字元流的類就不能用於產生通道;但是java.nio.channels.Channels類中提供了方法,可以在通道中產生Reader和Writer。 讀寫執行個體
public class GetChannel { private static final int BSIZE = 2014; public static void main(String[] args) throws IOException { /** * 寫資料, * 若已有內容,原內容會被覆蓋; * 若不存在檔案,則會建立新檔案並寫入新內容 */ FileChannel fc = new FileOutputStream("data.txt").getChannel(); fc.write(ByteBuffer.wrap("some text".getBytes())); fc.close(); /** * 新增內容至檔案末尾 */ fc = new RandomAccessFile("data.txt", "rw").getChannel(); fc.position(fc.size()); // 移動到檔案末尾 fc.write(ByteBuffer.wrap("Some more".getBytes())); fc.close(); /** * 讀取檔案 */ fc = new FileInputStream("data.txt").getChannel(); ByteBuffer buff = ByteBuffer.allocate(BSIZE); fc.read(buff); buff.flip(); while(buff.hasRemaining()){ System.out.print((char)buff.get()); } }}
channel:對於上述三種流類,getChannel()方法都可以獲得一個FileChannel。通道是一種相當基礎的東西:可以向它
傳送用於讀寫的ByteBuffer,並且可以鎖定檔案的某些地區用於獨佔式訪問 ByteBuffer:將位元組存放於ByteBuffer的方法之一是:使用一種
“put”方法直接對它們進行填充,填入一個或多個位元組,或基礎資料型別 (Elementary Data Type)的值。也可以
使用wrap()方法將已存在的位元組數組“封裝”到ByteBuffer中,這樣就把一個數組封裝為ByteBuffer緩衝器,一旦完成封裝,底層資料就可以通過緩衝區或者直接存取。我們稱第二種為
數組支援的ByteBuffer position:可以使用FileChannel的position方法來在檔案中移動FileChannel。在上例中是將其移動到檔案末尾,方便內容的增加。 allocate:對於唯讀訪問,我們要顯式使用靜態allocate()方法來分配ByteBuffer,nio的目標就是快速移動大量資料,因此ByteBuffer的大小就顯得尤為重要——事實上,這裡使用的1K可能比我們通常要使用的小一點(必須通過實際運行程式來找到最佳尺寸)。使用allocateDirect替代allocate,以產生一個與作業系統有更高耦合性的“直接”緩衝器還有可能達到更高的速度。但是,這種分配會增加開支,並且具體的實現也隨作業系統的不同而不同。 read()和flip():一旦調用read()來告知FileChannel向ByteBuffer儲存位元組,就必須調用緩衝器上的flip(),讓它做好讓別人讀取位元組的準備。如果打算使用緩衝器執行進一步的read()操作,我們也必須調用clear()來為每個read()做好準備。如下例:
public class ChannelCopy { private static final int BSIZE = 2014; public static void main(String[] args) throws IOException { FileChannel in = new FileInputStream("src/main/resources/in.txt").getChannel(); FileChannel out = new FileOutputStream("src/main/resources/out.txt").getChannel(); ByteBuffer buffer = ByteBuffer.allocate(BSIZE); while(in.read(buffer) != -1){ buffer.flip(); // 準備寫 out.write(buffer); buffer.clear(); // 準備讀 } /** * 常用的是直接使用transferTo 或者 transferFrom */// in.transferTo(0, in.size(), out);// 或者out.transferFrom(in, 0, in.size()); }}
這裡有兩個FileChannel,一個用於讀,一個用於寫。每次read()操作之後,就會將資料輸入到緩衝器中,flip()則是準備緩衝器以便它的資訊可以由write()提取。write()操作之後,資訊仍在緩衝區中,接著clear()操作則對所有的內部指標重新安排,以便緩衝器在另一個read()操作期間能夠做好接收資料的準備。
檔案內容複寫之類的操作,一般使用transferTo或者transferFrom方法