NIO-讀寫

NIO 讀寫

Github Demo

讀和寫是 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ū)中。
因此讀取文件涉及三個步驟:

  1. 從 FileInputStream 獲取 Channel;
  2. 創(chuàng)建 Buffer;
  3. 將數(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
        }
    }

結(jié)果
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容