NIO 讀寫
讀和寫是 I/O 的基本過程。從一個通道中讀取很簡單:只需創(chuàng)建一個緩沖區(qū),然后讓通道將數(shù)據(jù)讀到這個緩沖區(qū)中。寫入也相當簡單:創(chuàng)建一個緩沖區(qū),用數(shù)據(jù)填充它,然后讓通道用這些數(shù)據(jù)來執(zhí)行寫入操作。
從文件中讀取
從一個文件中讀取一些數(shù)據(jù)。
- I/O方式: 使用原來的 I/O,那么我們只需創(chuàng)建一個 FileInputStream 并從它那里讀取。
- NIO方式: 先從 FileInputStream 獲取一個 Channel 對象,然后使用這個通道來讀取數(shù)據(jù)。
在 NIO 系統(tǒng)中,任何時候執(zhí)行一個讀操作,都是從通道中讀取,但不是直接從通道讀取。因為所有數(shù)據(jù)最終都駐留在緩沖區(qū)中,所以是從通道讀到緩沖區(qū)中。
因此讀取文件涉及三個步驟:
- 從 FileInputStream 獲取 Channel;
- 創(chuàng)建 Buffer;
- 將數(shù)據(jù)從 Channel 讀到 Buffer 中。
三個步驟
- 第一步 獲取通道。我們從 FileInputStream 獲取通道:
FileInputStream fin = new FileInputStream( "readandshow.log" ); FileChannel fc = fin.getChannel();
- 第二步 創(chuàng)建緩沖區(qū):
ByteBuffer buffer = ByteBuffer.allocate(1024);
- 第三步 將數(shù)據(jù)從通道讀到緩沖區(qū)中,如下所示:
fc.read( buffer );
會注意到,我們不需要告訴通道要讀多少數(shù)據(jù)到緩沖區(qū)中。每一個緩沖區(qū)都有復雜的內(nèi)部統(tǒng)計機制,它會跟蹤已經(jīng)讀了多少數(shù)據(jù)以及還有多少空間可以容納更多的數(shù)據(jù)。關于緩沖區(qū)統(tǒng)計機制我們稍后再討論
@Test
public void testRead() {
try {
FileInputStream fin = new FileInputStream("/Users/dongsj/workspace/dsj/javaSpace/nettyDemo/src/test/resources/nio/readandshow.log");
FileChannel fileChannel = fin.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int result = fileChannel.read(buffer);
System.out.println("read : " + result);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
寫入文件
在 NIO 中寫入文件類似于從文件中讀取。
三個步驟
- 第一步 獲取通道。從 FileOutputStream 獲取一個通道:
FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" ); FileChannel fc = fout.getChannel();
- 第二步 創(chuàng)建緩沖區(qū)。創(chuàng)建一個緩沖區(qū)并在其中放入一些數(shù)據(jù),這里數(shù)據(jù)將從一個名為 message 的數(shù)組中取出,這個數(shù)組包含字符串 "Some bytes" 的 ASCII 字節(jié)。
ByteBuffer buffer = ByteBuffer.allocate( 1024 ); for (int i=0; i<message.length; ++i) { buffer.put( message[i] ); } buffer.flip();
buffer#flip()
: ;
buffer#put()
: 將指定的byte寫入緩沖區(qū),并將寫入位置遞增, 這個方法有其它重載方法;
flip()
源代碼可以看出,該方法將limit指向了緩沖區(qū)當前位置position
,并將position
設置為0,將mark
丟棄public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
- mark <= position <= limit <= capacity
- mark : 標示了緩沖區(qū)中執(zhí)行
reset
操作時,position應該置于的位置- position : 標示緩沖區(qū)中下一個能夠進行讀寫的位置
- limit : 標示緩沖區(qū)中第一個不能進行讀寫的位置
- capacity: 用來指定緩沖區(qū)的最大容量,它是不變的
allocate(1024)
public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); return new HeapByteBuffer(capacity, capacity); }
在調(diào)用allocate()方法分配緩沖區(qū)時,實際上將limit 和 capacity 設置成了同樣的大小。更多細節(jié)可以參考 Buffer & ByteBuffer源碼
- 第三步 寫入緩沖區(qū)中:
fc.write( buffer );
注意在這里同樣不需要告訴通道要寫入多數(shù)據(jù)。緩沖區(qū)的內(nèi)部統(tǒng)計機制會跟蹤它包含多少數(shù)據(jù)以及還有多少數(shù)據(jù)要寫入。
@Test
public void testWrite() {
byte[] message = "some bytes to write".getBytes();
try {
FileOutputStream fout = new FileOutputStream("/Users/dongsj/workspace/dsj/javaSpace/nettyDemo/src/test/resources/nio/writeshow.log");
FileChannel fileChannel = fout.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
for (int i=0; i < message.length; i++) {
buffer.put(message[i]);
}
buffer.flip();
fileChannel.write(buffer);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
讀&寫
將一個文件的所有內(nèi)容拷貝到另一個文件中。執(zhí)行三個基本操作:(1)創(chuàng)建一個Buffer,(2)從源文件中將數(shù)據(jù)讀到這個緩沖區(qū)中,(3)將緩沖區(qū)寫入目標文件。這個程序不斷重復,直到源文件結(jié)束。
程序讓我們看到如何檢查操作的狀態(tài),以及如何使用 clear() 和 flip() 方法重設緩沖區(qū),并準備緩沖區(qū)以便將新讀取的數(shù)據(jù)寫到另一個通道中。
-
讀寫
因為緩沖區(qū)會跟蹤它自己的數(shù)據(jù),所以程序的內(nèi)部循環(huán) (inner loop) 非常簡單,如下所示:fcin.read( buffer ); // 將數(shù)據(jù)從輸入通道 fcin 中讀入緩沖區(qū) fcout.write( buffer ); // 將這些數(shù)據(jù)寫到輸出通道 fcout
-
檢查狀態(tài)
檢查拷貝何時完成。當沒有更多的數(shù)據(jù)時,拷貝就算完成,并且可以在 read() 方法返回 -1 是判斷這一點:int r = fcin.read( buffer ); if (r==-1) { break; }
-
重設緩沖區(qū)
從輸入通道讀入緩沖區(qū)之前,我們調(diào)用 clear() 方法。同樣,在將緩沖區(qū)寫入輸出通道之前,我們調(diào)用 flip() 方法,如下所示:buffer.clear(); int r = fcin.read( buffer ); if (r==-1) { break; } buffer.flip(); fcout.write( buffer );
buffer#clear()
方法重設緩沖區(qū),使它可以接受讀入的數(shù)據(jù)
buffer#flip()
方法讓緩沖區(qū)可以將新讀入的數(shù)據(jù)寫入另一個通道。在netty中同樣有緩沖區(qū)
ByteBuf
,但是,并不需要使用flip()方法,因為ByteBuf
使用讀寫雙指針來表示數(shù)據(jù)的起至點,關于Netty后續(xù)將會涉及到。 關于netty
@Test
public void testReadAndWrite() {
FileInputStream fin;
FileOutputStream fout;
FileChannel finChannel, foutChannel;
try {
fin = new FileInputStream("/Users/dongsj/workspace/dsj/javaSpace/nettyDemo/src/test/resources/nio/readandshow.log");
fout = new FileOutputStream("/Users/dongsj/workspace/dsj/javaSpace/nettyDemo/src/test/resources/nio/readandshow.log_copy");
finChannel = fin.getChannel();
foutChannel = fout.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(10);
int count = -1;
do {
count = finChannel.read(buffer);
buffer.flip();
foutChannel.write(buffer);
buffer.clear();
} while (-1 != count);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// TODO release
}
}