说到高速缓存存储,处理读写文件,那就不得不说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,性能不一定最优,要权衡不同情况各种因素设置大小。