天天看點

Java的随機讀寫檔案-RandomAccessFile 與記憶體映射

RandomAccessFile

RandomAccessFile是用來通路那些儲存資料記錄的檔案的,你就可以用seek( )方法來通路記錄,并進行讀寫了。這些記錄的大小不必相同;但是其大小和位置必須是可知的。但是該類僅限于操作檔案。

RandomAccessFile不屬于InputStream和OutputStream類系的。實際上,除了實作DataInput和 DataOutput接口之外(DataInputStream和DataOutputStream也實作了這兩個接口),它和這兩個類系毫不相幹,甚至不使用InputStream和OutputStream類中已經存在的任何功能;它是一個完全獨立的類,所有方法(絕大多數都隻屬于它自己)都是從零開始寫的。這可能是因為RandomAccessFile能在檔案裡面前後移動,是以它的行為與其它的I/O類有些根本性的不同。總而言之,它是一個直接繼承Object的,獨立的類。

基本上,RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream結合起來,再加上它自己的一些方法,比如定位用的getFilePointer( ),在檔案裡移動用的seek( ),以及判斷檔案大小的length( )、skipBytes()跳過多少位元組數。此外,它的構造函數還要一個表示以隻讀方式("r"),還是以讀寫方式("rw")打開檔案的參數 (和C的fopen( )一模一樣)。它不支援隻寫檔案。

隻有RandomAccessFile才有seek搜尋方法,而這個方法也隻适用于檔案。BufferedInputStream有一個mark( )方法,你可以用它來設定标記(把結果儲存在一個内部變量裡),然後再調用reset( )傳回這個位置,但是它的功能太弱了,而且也不怎麼實用。

RandomAccessFile的絕大多數功能,但不是全部,已經被JDK 1.4的nio的"記憶體映射檔案(memory-mapped files)"給取代了,你該考慮一下是不是用"記憶體映射檔案"來代替RandomAccessFile了。

記憶體映射檔案

記憶體映射檔案能讓你建立和修改那些因為太大而無法放入記憶體的檔案。有了記憶體映射檔案,你就可以認為檔案已經全部讀進了記憶體,然後把它當成一個非常大的數組來通路。這種解決辦法能大大簡化修改檔案的代碼。

fileChannel.map(FileChannel.MapMode mode, long position, long size)将此通道的檔案區域直接映射到記憶體中。注意,你必須指明,它是從檔案的哪個位置開始映射的,映射的範圍又有多大;也就是說,它還可以映射一個大檔案的某個小片斷。

MappedByteBuffer是ByteBuffer的子類,是以它具備了ByteBuffer的所有方法,但新添了force()将緩沖區的内容強制重新整理到儲存設備中去、load()将儲存設備中的資料加載到記憶體中、isLoaded()位置記憶體中的資料是否與存儲設定上同步。這裡隻簡單地示範了一下put()和get()方法,除此之外,你還可以使用asCharBuffer( )之類的方法得到相應基本類型資料的緩沖視圖後,可以友善的讀寫基本類型資料。

Java代碼 

  1. import java.io.RandomAccessFile;
  2. import java.nio.MappedByteBuffer;
  3. import java.nio.channels.FileChannel;
  4. public class LargeMappedFiles {
  5. static int length = 0x8000000; // 128 Mb
  6. public static void main(String[] args) throws Exception {
  7. // 為了以可讀可寫的方式打開檔案,這裡使用RandomAccessFile來建立檔案。
  8. FileChannel fc = new RandomAccessFile("test.dat", "rw").getChannel();
  9. //注意,檔案通道的可讀可寫要建立在檔案流本身可讀寫的基礎之上
  10. MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, length);
  11. //寫128M的内容
  12. for (int i = 0; i < length; i++) {
  13. out.put((byte) 'x');
  14. }
  15. System.out.println("Finished writing");
  16. //讀取檔案中間6個位元組内容
  17. for (int i = length / 2; i < length / 2 + 6; i++) {
  18. System.out.print((char) out.get(i));
  19. }
  20. fc.close();
  21. }
  22. }

盡管映射寫似乎要用到FileOutputStream,但是映射檔案中的所有輸出 必須使用RandomAccessFile,但如果隻需要讀時可以使用FileInputStream,寫映射檔案時一定要使用随機通路檔案,可能寫時要讀的原因吧。