NIO入門系列之第5章:關於緩衝區的更多內容

第5章 關於緩衝區的更多內容

5.1  概述

到目前爲止,您已經學習了使用緩衝區進行日常工作所需要掌握的大部分內容。我們的例子沒怎麼超出標準的讀/寫過程種類,在原來的 I/O中可以像在 NIO 中一樣容易地實現這樣的標準讀寫過程。

本節將討論使用緩衝區的一些更復雜的方面,比如緩衝區分配、包裝和分片。我們還會討論 NIO 帶給 Java 平臺的一些新功能。您將學到如何創建不同類型的緩衝區以達到不同的目的,如可保護數據不被修改的只讀緩衝區,和直接映射到底層操作系統緩衝區的直接緩衝區。我們將在本節的最後介紹如何在 NIO 中創建內存映射文件。


5.2  緩衝區分配和包裝

在能夠讀和寫之前,必須有一個緩衝區。要創建緩衝區,您必須分配它。我們使用靜態方法 allocate() 來分配緩衝區:

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

allocate() 方法分配一個具有指定大小的底層數組,並將它包裝到一個緩衝區對象中在本例中是一個 ByteBuffer

您還可以將一個現有的數組轉換爲緩衝區,如下所示:

byte array[] = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap( array );

本例使用了 wrap() 方法將一個數組包裝爲緩衝區。必須非常小心地進行這類操作。一旦完成包裝,底層數據就可以通過緩衝區或者直接訪問。


5.3  緩衝區分片

slice() 方法根據現有的緩衝區創建一種子緩衝區。也就是說,它創建一個新的緩衝區,新緩衝區與原來的緩衝區的一部分共享數據。

使用例子可以最好地說明這點。讓我們首先創建一個長度爲 10 ByteBuffer

ByteBuffer buffer = ByteBuffer.allocate( 10 );

然後使用數據來填充這個緩衝區,在第 n 個槽中放入數字 n

for (int i=0; i<buffer.capacity(); ++i) {
     buffer.put( (byte)i );
}

現在我們對這個緩衝區分片,以創建一個包含槽 3 到槽 6 的子緩衝區。在某種意義上,子緩衝區就像原來的緩衝區中的一個窗口

窗口的起始和結束位置通過設置position limit 值來指定,然後調用 Buffer slice() 方法:

buffer.position( 3 );
buffer.limit( 7 );
ByteBuffer slice = buffer.slice();

片是緩衝區的子緩衝區。不過,片段緩衝區共享同一個底層數據數組,我們在下一節將會看到這一點。

5.4  緩衝區份片和數據共享

我們已經創建了原緩衝區的子緩衝區,並且我們知道緩衝區和子緩衝區共享同一個底層數據數組。讓我們看看這意味着什麼。

我們遍歷子緩衝區,將每一個元素乘以 11 來改變它。例如,5會變成 55

for (int i=0; i<slice.capacity(); ++i) {
     byte b = slice.get( i );
     b *= 11;
     slice.put( i, b );
}

最後,再看一下原緩衝區中的內容:

buffer.position( 0 );
buffer.limit( buffer.capacity() );
while (buffer.remaining()>0) {
     System.out.println( buffer.get() );
}

結果表明只有在子緩衝區窗口中的元素被改變了:

$ java SliceBuffer
0
1
2
33
44
55
66
7
8
9

緩衝區片對於促進抽象非常有幫助。可以編寫自己的函數處理整個緩衝區,而且如果想要將這個過程應用於子緩衝區上,您只需取主緩衝區的一個片,並將它傳遞給您的函數。這比編寫自己的函數來取額外的參數以指定要對緩衝區的哪一部分進行操作更容易。

5.5  只讀緩衝區

只讀緩衝區非常簡單您可以讀取它們,但是不能向它們寫入。可以通過調用緩衝區的 asReadOnlyBuffer() 方法,將任何常規緩衝區轉換爲只讀緩衝區,這個方法返回一個與原緩衝區完全相同的緩衝區(並與其共享數據),只不過它是隻讀的。

只讀緩衝區對於保護數據很有用。在將緩衝區傳遞給某個對象的方法時,您無法知道這個方法是否會修改緩衝區中的數據。創建一個只讀的緩衝區可以保證該緩衝區不會被修改。

不能將只讀的緩衝區轉換爲可寫的緩衝區。


5.6  直接和間接緩衝區

另一種有用的ByteBuffer 是直接緩衝區。直接緩衝區是爲加快 I/O 速度,而以一種特殊的方式分配其內存的緩衝區。

實際上,直接緩衝區的準確定義是與實現相關的。Sun 的文檔是這樣描述直接緩衝區的:

給定一個直接字節緩衝區,Java 虛擬機將盡最大努力直接對它執行本機 I/O 操作。也就是說,它會在每一次調用底層操作系統的本機 I/O 操作之前(或之後),嘗試避免將緩衝區的內容拷貝到一箇中間緩衝區中(或者從一箇中間緩衝區中拷貝數據)


您可以在例子程序FastCopyFile.java 中看到直接緩衝區的實際應用,這個程序是 CopyFile.java 的另一個版本,它使用了直接緩衝區以提高速度。

還可以用內存映射文件創建直接緩衝區。

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class FastCopyFile
{
  static public void main( String args[] ) throws Exception {
    if (args.length<2) {
      System.err.println( "Usage: java FastCopyFile infile outfile" );
      System.exit( 1 );
    }
    String infile = args[0];
    String outfile = args[1];
    FileInputStream fin = new FileInputStream( infile );
    FileOutputStream fout = new FileOutputStream( outfile );
    FileChannel fcin = fin.getChannel();
    FileChannel fcout = fout.getChannel();
    ByteBuffer buffer = ByteBuffer.allocateDirect( 1024 );
    while (true) {
      buffer.clear();
      int r = fcin.read( buffer );
      if (r==-1) {
        break;
      }
      buffer.flip();
      fcout.write( buffer );
    }
  }
}

5.7  內存映射文件 I/O

內存映射文件 I/O 是一種讀和寫文件數據的方法,它可以比常規的基於流或者基於通道的 I/O 快得多。

內存映射文件 I/O 是通過使文件中的數據神奇般地出現爲內存數組的內容來完成的。這其初聽起來似乎不過就是將整個文件讀到內存中,但是事實上並不是這樣。一般來說,只有文件中實際讀取或者寫入的部分纔會送入(或者映射)到內存中。

內存映射並不真的神奇或者多麼不尋常。現代操作系統一般根據需要將文件的部分映射爲內存的部分,從而實現文件系統。Java 內存映射機制不過是在底層操作系統中可以採用這種機制時,提供了對該機制的訪問。

儘管創建內存映射文件相當簡單,但是向它寫入可能是危險的。僅只是改變數組的單個元素這樣的簡單操作,就可能會直接修改磁盤上的文件。修改數據與將數據保存到磁盤是沒有分開的。



5.8  將文件映射到內存

瞭解內存映射的最好方法是使用例子。在下面的例子中,我們要將一個 FileChannel (它的全部或者部分)映射到內存中。爲此我們將使用FileChannel.map() 方法。下面代碼行將文件的前 1024 個字節映射到內存中:

MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE,0, 1024 );

map() 方法返回一個 MappedByteBuffer,它是 ByteBuffer 的子類。因此,您可以像使用其他任何 ByteBuffer 一樣使用新映射的緩衝區,操作系統會在需要時負責執行行映射。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章