說到高速緩存存儲,處理讀寫文件,那就不得不說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,性能不一定最優,要權衡不同情況各種因素設置大小。