標籤:
說到快取儲存,處理讀寫檔案,那就不得不說MappedByteBuffer。
看了好多文章以後寫一下自己的總結。
在這裡先介紹一下相關的類與方法。
先說一下Buffer、ByteBuffer、MappedByteBuffer這幾個類之間的關係。
public abstract class Buffer { // Invariants: mark <= position <= limit <= capacity private int mark = -1; private int position = 0; private int limit; private int capacity; long address; ......}public abstract class ByteBuffer extends Buffer implements Comparable { // These fields are declared here rather than in Heap-X-Buffer in order to // reduce the number of virtual method invocations needed to access these // values, which is especially costly when coding small buffers. // final byte[] hb; // Non-null only for heap buffers final int offset; boolean isReadOnly; // Valid only for heap buffers boolean bigEndian; boolean nativeByteOrder; ......}//位元組數組final byte[] hb就是所指的那塊記憶體緩衝區public abstract class MappedByteBuffer extends ByteBuffer{ private final FileDescriptor fd; ......}
public abstract class MappedByteBuffer extends ByteBuffer 直接位元組緩衝區,其內容是檔案的記憶體映射地區。
映射的位元組緩衝區是通過 FileChannel.map 方法建立的。此類用特定於記憶體對應檔地區的操作擴充 ByteBuffer 類。
映射的位元組緩衝區和它所表示的檔案對應關係在該緩衝區本身成為記憶體回收緩衝區之前一直保持有效。
映射的位元組緩衝區的內容可以隨時更改,例如,在此程式或另一個程式更改了對應的對應檔地區的內容的情況下。這些更改是否發生(以及何時發生)與作業系統無關,因此是未指定的。
全部或部分映射的位元組緩衝區可能隨時成為不可訪問的,例如,如果我們截取映射的檔案。試圖訪問映射的位元組緩衝區的不可訪問地區將不會更改緩衝區的內容,並導致在訪問時或訪問後的某個時刻拋出未指定的異常。因此強烈推薦採取適當的預防措施,以避免此程式或另一個同時啟動並執行程式對映射的檔案執行操作(讀寫檔案內容除外)。
除此之外,映射的位元組緩衝區的功能與普通的直接位元組緩衝區完全相同。
public RandomAccessFile(File file, String mode)throws FileNotFoundException
建立從中讀取和向其中寫入(可選)的隨機訪問檔案流,該檔案由 File 參數指定。將建立一個新的 FileDescriptor 對象來表示此檔案的串連。
r" 以唯讀方式開啟。調用結果對象的任何 write 方法都將導致拋出 IOException。
"rw" 開啟以便讀取和寫入。如果該檔案尚不存在,則嘗試建立該檔案。
"rws" 開啟以便讀取和寫入,對於 "rw",還要求對檔案的內容或中繼資料的每個更新都同步寫入到底層存放裝置。
"rwd" 開啟以便讀取和寫入,對於 "rw",還要求對檔案內容的每個更新都同步寫入到底層存放裝置。
public final FileChannel getChannel()返回與此檔案關聯的唯一 FileChannel 對象。
返回通道的 java.nio.channels.FileChannel#position()position 將始終等於 getFilePointer 方法返回的此對象的檔案指標位移量。顯式或者通過讀取或寫入位元組來更改此對象的檔案指標位移量將更改通道的位置,反之亦然。通過此對象更改此檔案的長度將更改通過檔案通道看到的長度,反之亦然。
public abstract MappedByteBuffer map(FileChannel.MapMode mode,long position,long size)throws IOException將此通道的檔案地區直接映射到記憶體中。
mode - 根據是按唯讀、讀取/寫入或專用(寫入時拷貝)來對應檔,分別為 FileChannel.MapMode類中所定義的 READ_ONLY、READ_WRITE 或 PRIVATE 之一
position - 檔案中的位置,映射地區從此位置開始;必須為非負數
size - 要映射的地區大小;必須為非負數且不大於 Integer.MAX_VALUE
下面一個啟動並執行例子:
package com.tzx.ne;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.RandomAccessFile;import java.nio.Buffer;import java.nio.ByteBuffer;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;public class MappedByteBufferDemo { public static void main(String[] args) throws Exception{ /** * output: 0.001s(讀) * input: 0.11s(寫) * */ MappedByteBufferTest(); /** * size=1024*8 * out: 0.0s * input: 0.014s * */ /** * size=1024*1024*8 * output: 0.01s * input: 0.014s * */ /** * size=80 * output: 0.0s * input: 0.546s * */ //BufferTest(); /** * time: 0.585s * */ //BufferedInputStreamTest(); } /* * 測試結果與Buffer size有關 */ // 1、使用MappedByteBuffer: 0.7s public static void MappedByteBufferTest() throws Exception{ String srcFile = "F:\\Ebook\\偷天.txt"; String destFile = "F:\\Ebook\\toutian.txt"; RandomAccessFile rafi = new RandomAccessFile(srcFile, "r"); RandomAccessFile rafo = new RandomAccessFile(destFile, "rw"); FileChannel fci = rafi.getChannel(); FileChannel fco = rafo.getChannel(); long size = fci.size(); byte b; long start = System.currentTimeMillis(); MappedByteBuffer mbbi = fci.map(FileChannel.MapMode.READ_ONLY, 0, size); System.out.println("output: " + (double) (System.currentTimeMillis() - start) / 1000 + "s"); MappedByteBuffer mbbo = fco.map(FileChannel.MapMode.READ_WRITE, 0, size); start = System.currentTimeMillis(); for (int i = 0; i < size; i++) { b = mbbi.get(i); mbbo.put(i, b); } fci.close(); fco.close(); rafi.close(); rafo.close(); System.out.println("input: " + (double) (System.currentTimeMillis() - start) / 1000 + "s"); } // 2、自己處理Buffer(RandomAccessFile): 0.13s public static void BufferTest() throws Exception{ String srcFile = "F:\\Ebook\\偷天.txt"; String destFile = "F:\\Ebook\\toutian.txt"; RandomAccessFile rafi = new RandomAccessFile(srcFile, "r"); RandomAccessFile rafo = new RandomAccessFile(destFile, "rw"); byte[] buf = new byte[80]; long start = System.currentTimeMillis(); int c = rafi.read(buf); System.out.println("output: " + (double) (System.currentTimeMillis() - start) / 1000 + "s"); start = System.currentTimeMillis(); while (c > 0) { if (c == buf.length) { rafo.write(buf); } else { rafo.write(buf, 0, c); } c = rafi.read(buf); } System.out.println("input: " + (double) (System.currentTimeMillis() - start) / 1000 + "s"); rafi.close(); rafo.close(); } // 3、BufferedInputStream&BufferedOutputStream: 3.02s public static void BufferedInputStreamTest() throws Exception{ String srcFile = "F:\\Ebook\\偷天.txt"; String destFile = "F:\\Ebook\\toutian.txt"; FileInputStream rafi = new FileInputStream(srcFile); FileOutputStream rafo = new FileOutputStream(destFile); BufferedInputStream bis = new BufferedInputStream(rafi, 8192); BufferedOutputStream bos = new BufferedOutputStream(rafo, 8192); long size = rafi.available(); long start = System.currentTimeMillis(); for (int i = 0; i < size; i++) { byte b = (byte) bis.read(); bos.write(b); } rafi.close(); rafo.close(); System.out.println("time: " + (double) (System.currentTimeMillis() - start) / 1000 + "s"); }}
總結:
1、RandomAccessFile是Java輸入輸出資料流體系中功能最豐富的檔案內容訪問類,他提供 了眾多的方法來訪問檔案,它既可以讀取檔案的內容,也可以說向檔案輸出資料,本身不帶緩衝讀寫,和FileInputStream、FileOutputStream等一樣,直接按位元組讀寫時,效能不可接受;
2、使用MappedByteBuffer讀寫,固然效能會得到極大提升;其實只要自己處理緩衝,效能都會有非常大的提升,比如以下兩種方式中第一種使用了MappedByteBuffer,第二種自己進行緩衝處理後,對於幾兆的檔案,後者的效率甚至高於前者,可以從幾個size大小看出運行速度,當size較大的時候一次性的讀取速度是慢些,但是整體的效率非常之高。
3、BufferedXXXX之類的緩衝流,如果僅使用預設的buffer size,效能不一定最優,要權衡不同情況各種因素設定大小。
MappedByteBuffer快取檔案、RandomAccessFile隨機訪問