1. 程式人生 > >Java使用RandomAccessFile讀寫檔案

Java使用RandomAccessFile讀寫檔案

Java 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了。

import java.io.IOException;  
import java.io.RandomAccessFile;  

public class TestRandomAccessFile {  
    public static void main(String[] args) throws IOException {  
        RandomAccessFile rf = new
RandomAccessFile("rtest.dat", "rw"); for (int i = 0; i < 10; i++) { //寫入基本型別double資料 rf.writeDouble(i * 1.414); } rf.close(); rf = new RandomAccessFile("rtest.dat", "rw"); //直接將檔案指標移到第5個double資料後面 rf.seek(5 * 8); //覆蓋第6個double資料 rf.writeDouble(47.0001); rf.close(); rf = new RandomAccessFile("rtest.dat", "r"); for (int i = 0; i < 10; i++) { System.out.println("Value " + i + ": " + rf.readDouble()); } rf.close(); } }

記憶體對映檔案

記憶體對映檔案能讓你建立和修改那些因為太大而無法放入記憶體的檔案。有了記憶體對映檔案,你就可以認為檔案已經全部讀進了記憶體,然後把它當成一個非常大的陣列來訪問。這種解決辦法能大大簡化修改檔案的程式碼。
fileChannel.map(FileChannel.MapMode mode, long position, long size)將此通道的檔案區域直接對映到記憶體中。注意,你必須指明,它是從檔案的哪個位置開始對映的,對映的範圍又有多大;也就是說,它還可以對映一個大檔案的某個小片斷。

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

import java.io.RandomAccessFile;  
import java.nio.MappedByteBuffer;  
import java.nio.channels.FileChannel;  

public class LargeMappedFiles {  
    static int length = 0x8000000; // 128 Mb  

    public static void main(String[] args) throws Exception {  
        // 為了以可讀可寫的方式開啟檔案,這裡使用RandomAccessFile來建立檔案。  
        FileChannel fc = new RandomAccessFile("test.dat", "rw").getChannel();  
        //注意,檔案通道的可讀可寫要建立在檔案流本身可讀寫的基礎之上  
        MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, length);  
        //寫128M的內容  
        for (int i = 0; i < length; i++) {  
            out.put((byte) 'x');  
        }  
        System.out.println("Finished writing");  
        //讀取檔案中間6個位元組內容  
        for (int i = length / 2; i < length / 2 + 6; i++) {  
            System.out.print((char) out.get(i));  
        }  
        fc.close();  
    }  
}

儘管對映寫似乎要用到FileOutputStream,但是對映檔案中的所有輸出 必須使用RandomAccessFile,但如果只需要讀時可以使用FileInputStream,寫對映檔案時一定要使用隨機訪問檔案,可能寫時要讀的原因吧。

該程式建立了一個128Mb的檔案,如果一次性讀到記憶體可能導致記憶體溢位,但這裡訪問好像只是一瞬間的事,這是因為,真正調入記憶體的只是其中的一小部分,其餘部分則被放在交換檔案上。這樣你就可以很方便地修改超大型的檔案了(最大可以到2 GB)。注意,Java是呼叫作業系統的”檔案對映機制”來提升效能的。

RandomAccessFile類的應用:

/* 
 * 程式功能:演示了RandomAccessFile類的操作,同時實現了一個檔案複製操作。 
 */  
package com.lwj.demo;  

import java.io.*;  

public class RandomAccessFileDemo {  
 public static void main(String[] args) throws Exception {  
  RandomAccessFile file = new RandomAccessFile("file", "rw");  
  // 以下向file檔案中寫資料  
  file.writeInt(20);// 佔4個位元組  
  file.writeDouble(8.236598);// 佔8個位元組  
  file.writeUTF("這是一個UTF字串");// 這個長度寫在當前檔案指標的前兩個位元組處,可用readShort()讀取  
  file.writeBoolean(true);// 佔1個位元組  
  file.writeShort(395);// 佔2個位元組  
  file.writeLong(2325451l);// 佔8個位元組  
  file.writeUTF("又是一個UTF字串");  
  file.writeFloat(35.5f);// 佔4個位元組  
  file.writeChar('a');// 佔2個位元組  

  file.seek(0);// 把檔案指標位置設定到檔案起始處  

  // 以下從file檔案中讀資料,要注意檔案指標的位置  
  System.out.println("——————從file檔案指定位置讀資料——————");  
  System.out.println(file.readInt());  
  System.out.println(file.readDouble());  
  System.out.println(file.readUTF());  

  file.skipBytes(3);// 將檔案指標跳過3個位元組,本例中即跳過了一個boolean值和short值。  
  System.out.println(file.readLong());  

  file.skipBytes(file.readShort()); // 跳過檔案中“又是一個UTF字串”所佔位元組,注意readShort()方法會移動檔案指標,所以不用加2。  
  System.out.println(file.readFloat());  

  //以下演示檔案複製操作  
  System.out.println("——————檔案複製(從file到fileCopy)——————");  
  file.seek(0);  
  RandomAccessFile fileCopy=new RandomAccessFile("fileCopy","rw");  
  int len=(int)file.length();//取得檔案長度(位元組數)  
  byte[] b=new byte[len];  
  file.readFully(b);  
  fileCopy.write(b);  
  System.out.println("複製完成!");  
 }  
}

RandomAccessFile 插入寫示例:

/** 
 *  
 * @param skip 跳過多少過位元組進行插入資料 
 * @param str 要插入的字串 
 * @param fileName 檔案路徑 
 */  
public static void beiju(long skip, String str, String fileName){  
    try {  
        RandomAccessFile raf = new RandomAccessFile(fileName,"rw");  
        if(skip <  0 || skip > raf.length()){  
            System.out.println("跳過位元組數無效");  
            return;  
        }  
        byte[] b = str.getBytes();  
        raf.setLength(raf.length() + b.length);  
        for(long i = raf.length() - 1; i > b.length + skip - 1; i--){  
            raf.seek(i - b.length);  
            byte temp = raf.readByte();  
            raf.seek(i);  
            raf.writeByte(temp);  
        }  
        raf.seek(skip);  
        raf.write(b);  
        raf.close();  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
}

利用RandomAccessFile實現檔案的多執行緒下載,即多執行緒下載一個檔案時,將檔案分成幾塊,每塊用不同的執行緒進行下載。下面是一個利用多執行緒在寫檔案時的例子,其中預先分配檔案所需要的空間,然後在所分配的空間中進行分塊,然後寫入:

import java.io.FileNotFoundException;  
import java.io.IOException;  
import java.io.RandomAccessFile;  

/** 
 * 測試利用多執行緒進行檔案的寫操作 
 */  
public class Test {  

    public static void main(String[] args) throws Exception {  
        // 預分配檔案所佔的磁碟空間,磁碟中會建立一個指定大小的檔案  
        RandomAccessFile raf = new RandomAccessFile("D://abc.txt", "rw");  
        raf.setLength(1024*1024); // 預分配 1M 的檔案空間  
        raf.close();  

        // 所要寫入的檔案內容  
        String s1 = "第一個字串";  
        String s2 = "第二個字串";  
        String s3 = "第三個字串";  
        String s4 = "第四個字串";  
        String s5 = "第五個字串";  

        // 利用多執行緒同時寫入一個檔案  
        new FileWriteThread(1024*1,s1.getBytes()).start(); // 從檔案的1024位元組之後開始寫入資料  
        new FileWriteThread(1024*2,s2.getBytes()).start(); // 從檔案的2048位元組之後開始寫入資料  
        new FileWriteThread(1024*3,s3.getBytes()).start(); // 從檔案的3072位元組之後開始寫入資料  
        new FileWriteThread(1024*4,s4.getBytes()).start(); // 從檔案的4096位元組之後開始寫入資料  
        new FileWriteThread(1024*5,s5.getBytes()).start(); // 從檔案的5120位元組之後開始寫入資料  
    }  

    // 利用執行緒在檔案的指定位置寫入指定資料  
    static class FileWriteThread extends Thread{  
        private int skip;  
        private byte[] content;  

        public FileWriteThread(int skip,byte[] content){  
            this.skip = skip;  
            this.content = content;  
        }  

        public void run(){  
            RandomAccessFile raf = null;  
            try {  
                raf = new RandomAccessFile("D://abc.txt", "rw");  
                raf.seek(skip);  
                raf.write(content);  
            } catch (FileNotFoundException e) {  
                e.printStackTrace();  
            } catch (IOException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            } finally {  
                try {  
                    raf.close();  
                } catch (Exception e) {  
                }  
            }  
        }  
    }  

}