java大文件讀寫操作


轉載自:http://blog.csdn.net/akon_vm/article/details/7429245

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();  
    }  
} 

內存映射文件

 內存映射:通過內存映射文件機制訪問一個文件會比使用常規方法讀寫高效得多,甚至比使用通道的效率都高,因為不需要做明確的系統調用,並且操作系統的虛擬內存會自動緩存內存頁,不會消耗jvm的堆內存。內存映射的具體介紹參考:JAVA NIO之淺談內存映射文件原理與DirectMemory

內存映射文件能讓你創建和修改那些因為太大而無法放入內存的文件。有了內存映射文件,你就可以認為文件已經全部讀進了內存,然后把它當成一個非常大的數組來訪問。這種解決辦法能大大簡化修改文件的代碼。
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是調用操作系統的"文件映射機制"來提升性能的。

 注:使用MapperByteBuffer雖然讀寫文件速度很快,但也存在一些問題,主要就是內存占用和文件關閉等不確定問題。因為被MappedByteBuffer打開的文件只有在垃圾收集回收時才會被關閉,而這個垃圾收集回收的點是不確定的,在javadoc里是這么說的:A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself  is garbage-collected.【映射的字節緩沖區和它表示的文件映射保持有效,直到緩沖區本身被垃圾收集回收。】,所以我們文件讀寫完成后,要手動釋放MapperByteBuffer資源,具體參考:java大文件讀寫操作,java nio 之MappedByteBuffer,高效文件/內存映射

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) {  
                }  
            }  
        }  
    }  
  
} 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM