Java使用記憶體對映實現大檔案的上傳

tobacco的專欄發表於2015-01-11

在處理大檔案時,如果利用普通的FileInputStream 或者FileOutputStream 抑或RandomAccessFile 來進行頻繁的讀寫操作,都將導致程式因頻繁讀寫外存而降低速度.如下為一個對比實驗。

package test;  

import java.io.BufferedInputStream;  
import java.io.FileInputStream;  
import java.io.FileNotFoundException;  
import java.io.IOException;  
import java.io.RandomAccessFile;  
import java.nio.MappedByteBuffer;  
import java.nio.channels.FileChannel;  

public class Test {  

    public static void main(String[] args) {  
        try {  
            FileInputStream fis=new FileInputStream("/home/tobacco/test/res.txt");  
            int sum=0;  
            int n;  
            long t1=System.currentTimeMillis();  
            try {  
                while((n=fis.read())>=0){  
                    sum+=n;  
                }  
            } catch (IOException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            long t=System.currentTimeMillis()-t1;  
            System.out.println("sum:"+sum+"  time:"+t);  
        } catch (FileNotFoundException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  

        try {  
            FileInputStream fis=new FileInputStream("/home/tobacco/test/res.txt");  
            BufferedInputStream bis=new BufferedInputStream(fis);  
            int sum=0;  
            int n;  
            long t1=System.currentTimeMillis();  
            try {  
                while((n=bis.read())>=0){  
                    sum+=n;  
                }  
            } catch (IOException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            long t=System.currentTimeMillis()-t1;  
            System.out.println("sum:"+sum+"  time:"+t);  
        } catch (FileNotFoundException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  

        MappedByteBuffer buffer=null;  
        try {  
            buffer=new RandomAccessFile("/home/tobacco/test/res.txt","rw").getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 1253244);  
            int sum=0;  
            int n;  
            long t1=System.currentTimeMillis();  
            for(int i=0;i<1253244;i++){  
                n=0x000000ff&buffer.get(i);  
                sum+=n;  
            }  
            long t=System.currentTimeMillis()-t1;  
            System.out.println("sum:"+sum+"  time:"+t);  
        } catch (FileNotFoundException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  

    }  

}

測試檔案為一個大小為1253244位元組的檔案。測試結果:

sum:220152087 time:1464  
sum:220152087 time:72  
sum:220152087 time:25

說明讀資料無誤。刪去其中的資料處理部分。

package test;  

import java.io.BufferedInputStream;  
import java.io.FileInputStream;  
import java.io.FileNotFoundException;  
import java.io.IOException;  
import java.io.RandomAccessFile;  
import java.nio.MappedByteBuffer;  
import java.nio.channels.FileChannel;  

public class Test {  

    public static void main(String[] args) {  
        try {  
            FileInputStream fis=new FileInputStream("/home/tobacco/test/res.txt");  
            int sum=0;  
            int n;  
            long t1=System.currentTimeMillis();  
            try {  
                while((n=fis.read())>=0){  
                    //sum+=n;  
                }  
            } catch (IOException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            long t=System.currentTimeMillis()-t1;  
            System.out.println("sum:"+sum+"  time:"+t);  
        } catch (FileNotFoundException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  

        try {  
            FileInputStream fis=new FileInputStream("/home/tobacco/test/res.txt");  
            BufferedInputStream bis=new BufferedInputStream(fis);  
            int sum=0;  
            int n;  
            long t1=System.currentTimeMillis();  
            try {  
                while((n=bis.read())>=0){  
                    //sum+=n;  
                }  
            } catch (IOException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            long t=System.currentTimeMillis()-t1;  
            System.out.println("sum:"+sum+"  time:"+t);  
        } catch (FileNotFoundException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  

        MappedByteBuffer buffer=null;  
        try {  
            buffer=new RandomAccessFile("/home/tobacco/test/res.txt","rw").getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 1253244);  
            int sum=0;  
            int n;  
            long t1=System.currentTimeMillis();  
            for(int i=0;i<1253244;i++){  
                //n=0x000000ff&buffer.get(i);  
                //sum+=n;  
            }  
            long t=System.currentTimeMillis()-t1;  
            System.out.println("sum:"+sum+"  time:"+t);  
        } catch (FileNotFoundException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  

    }  

}

測試結果:

sum:0 time:1458  
sum:0 time:67  
sum:0 time:8

由此可見,將檔案部分或者全部對映到記憶體後進行讀寫,速度將提高很多。

這是因為記憶體對映檔案首先將外存上的檔案對映到記憶體中的一塊連續區域,被當成一個位元組陣列進行處理,讀寫操作直接對記憶體進行操作,而後再將記憶體區域重新對映到外存檔案,這就節省了中間頻繁的對外存進行讀寫的時間,大大降低了讀寫時間。

相關文章