1. 程式人生 > >Java NIO 學習筆記(二)----聚集和分散,通道到通道

Java NIO 學習筆記(二)----聚集和分散,通道到通道

目錄:
Java NIO 學習筆記(一)----概述,Channel/Buffer
Java NIO 學習筆記(二)----聚集和分散,通道到通道

Scatter / Gather 通道的聚集和分散操作

NIO 具有內建的 scatter/gather 支援,用於描述讀取和寫入通道的操作。

  • 分散(scatter)地從 Channel 中讀取是將資料讀入多個 Buffer 的操作。 因此,通道將來自通道的資料“分散”到多個緩衝區中。
  • 聚集(gather)地寫入 Channel 是將來自多個緩衝區的資料寫入單個通道的操作。 因此,通道將來自多個緩衝區的資料“收集”到同一個通道中。

通道的聚集和分散操作在需要將傳輸的資料分開處理的場合非常有用,例如,如果訊息由標題和正文組成,則可以將標題和正文保留在單獨的緩衝區中,這樣做可以更容易處理標題和正文。

Scattering Reads 分散讀取

是指將資料從單個通道讀入多個緩衝區:

image

下面是一個程式碼示例,演示如何執行分散讀取:

public class ScatteringReads {
    public static void main(String[] args) throws IOException {

        ByteBuffer buffer1 = ByteBuffer.allocate(5); // 分配第一個緩衝區,大小為 5
        ByteBuffer buffer2 = ByteBuffer.allocate(128);
        ByteBuffer[] buffers = {buffer1, buffer2}; // 兩個緩衝區的陣列

        File file = new File("D:\\test\\1.txt"); // 檔案內容是 012345678
        RandomAccessFile accessFile = new RandomAccessFile(file, "rw");
        FileChannel channel = accessFile.getChannel();

        long data = channel.read(buffers); // 一次性把通道的資料讀入2個緩衝區
        System.out.println("Read: " + data); // Read 9

        System.out.println("開始讀取第一個 buffer :");
        buffer1.flip(); // 將 buffer 從寫入模式切換為讀取模式
        while (buffer1.hasRemaining()) {
            System.out.print((char) buffer1.get()); // 每次讀取1byte,輸出 01234
        }

        System.out.println("\n開始讀取第二個 buffer :");

        buffer2.flip();
        while (buffer2.hasRemaining()) {
            System.out.print((char) buffer2.get()); // 輸出 5678
        }
    }
}

將會輸出:

Read: 9
開始讀取第一個 buffer :
01234
開始讀取第二個 buffer :
5678

注意多個緩衝區首先插入到陣列中,然後將陣列作為引數傳遞給 channel.read() 方法。 然後,read()方法按照緩衝區在陣列中出現的順序從通道寫入資料。 一旦緩衝區已滿,通道就會繼續填充下一個緩衝區。
分散讀取在移動到下一個緩衝區之前,必須先填充慢前一個緩衝區,這意味著它不適合大小不固定的訊息。

Gathering Writes 聚集寫入

“聚集寫入”將來自多個緩衝區的資料寫入單個通道:

image

一個示例:

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
//write data into buffers...

channel.write(bufferArray);

將緩衝區陣列傳遞給 write() 方法,該方法按照在陣列的順序寫入緩衝區的內容到單個通道,注意僅寫入緩衝區的 position 和 limit 之間的資料。 因此,如果緩衝區的容量為 128 位元組,但只包含 58 位元組的內容,則只有 58 位元組從該緩衝區寫入通道。 因此,與 Scattering Reads 相比,Gathering Writes 可以適應大小不固定的資料,因為它只把包含內容部分的緩衝區寫入到通道。

Channel to Channel 通道到通道傳輸

在 NIO 中,如果其中一個通道是 FileChannel ,可以直接將資料從一個通道傳輸到另一個通道。 FileChannel 類有一個 transferTo() 和 transferFrom() 方法。

transferFrom() 和 transferTo()

FileChannel 物件的 transferFrom() 方法將資料從源通道傳輸到 FileChannel。 這是一個簡單的例子:

public class TransfetExample {
    public static void main(String[] args) throws IOException {
        RandomAccessFile fromFile = new RandomAccessFile("D:\\test\\input.txt", "rw");
        FileChannel fromChannel = fromFile.getChannel();

        RandomAccessFile toFile = new RandomAccessFile("D:\\test\\receive.txt", "rw");
        FileChannel toChannel = toFile.getChannel();

        long position = 0;
        long count = fromChannel.size();

        toChannel.transferFrom(fromChannel, position, count);
    }
}

引數 position 和 count,告訴目標檔案中開始寫入的位置以及最大傳輸的位元組數(總數)。 如果源通道的位元組數少於 count ,則傳輸實際位元組數。

此外,一些 SocketChannel 實現可能現在只傳輸 SocketChannel 在其內部緩衝區中準備好的資料 - 即使 SocketChannel 可能稍後有更多可用資料。 因此,它可能不會將請求的整個資料(count)從 SocketChannel 傳輸到 FileChannel 。

transferTo() 方法的效果除了目標和引數位置不一致,其他部分同 transferFrom() 方法一樣,上面程式碼如果換成執行 fromChannel.transferTo(position, count, toChannel); input.txt 的內容同樣會被立即複製到 receive.txt。