无论是哪个速度都是十分惊人的,但是 MappedByteBuffer 也有不足,就是在数据量很小的时候,表现比较糟糕,那是因为 direct buffer 的初始化时间较长,所以建议大家只有在数据量较大的时候,在用 MappedByteBuffer。
map过程FileChannel提供了map方法把文件映射到虚拟内存,通常情况可以映射整个文件,如果文件比较大,可以进行分段映射。
FileChannel中的几个变量:
MapMode mode:内存映像文件访问的方式,也就是上面说的三种方式。
position:文件映射时的起始位置。
allocationGranularity:Memory allocation size for mapping buffers,通过native函数initIDs初始化。
接下去通过分析源码,了解一下map过程的内部实现。通过RandomAccessFile获取FileChannel。
public final FileChannel getChannel() { synchronized (this) { if (channel == null) { channel = FileChannelImpl.open(fd, path, true, rw, this); } return channel; } }上述实现可以看出,由于synchronized ,只有一个线程能够初始化FileChannel。通过FileChannel.map方法,把文件映射到虚拟内存,并返回逻辑地址address,实现如下:
public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { int pagePosition = (int)(position % allocationGranularity); long mapPosition = position - pagePosition; long mapSize = size + pagePosition; try { addr = map0(imode, mapPosition, mapSize); } catch (OutOfMemoryError x) { System.gc(); try { Thread.sleep(100); } catch (InterruptedException y) { Thread.currentThread().interrupt(); } try { addr = map0(imode, mapPosition, mapSize); } catch (OutOfMemoryError y) { // After a second OOME, fail throw new IOException("Map failed", y); } } int isize = (int)size; Unmapper um = new Unmapper(addr, mapSize, isize, mfd); if ((!writable) || (imode == MAP_RO)) { return Util.newMappedByteBufferR(isize, addr + pagePosition, mfd, um); } else { return Util.newMappedByteBuffer(isize, addr + pagePosition, mfd, um); } }上述代码可以看出,最终map通过native函数map0完成文件的映射工作。
如果第一次文件映射导致OOM,则手动触发垃圾回收,休眠100ms后再次尝试映射,如果失败,则抛出异常。
通过newMappedByteBuffer方法初始化MappedByteBuffer实例,不过其最终返回的是DirectByteBuffer的实例,实现如下:
static MappedByteBuffer newMappedByteBuffer(int size, long addr, FileDescriptor fd, Runnable unmapper) { MappedByteBuffer dbb; if (directByteBufferConstructor == null) initDBBConstructor(); dbb = (MappedByteBuffer)directByteBufferConstructor.newInstance( new Object[] { new Integer(size), new Long(addr), fd, unmapper } return dbb; } // 访问权限 private static void initDBBConstructor() { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { Class<?> cl = Class.forName("java.nio.DirectByteBuffer"); Constructor<?> ctor = cl.getDeclaredConstructor( new Class<?>[] { int.class, long.class, FileDescriptor.class, Runnable.class }); ctor.setAccessible(true); directByteBufferConstructor = ctor; }}); }由于FileChannelImpl和DirectByteBuffer不在同一个包中,所以有权限访问问题,通过AccessController类获取DirectByteBuffer的构造器进行实例化。
DirectByteBuffer是MappedByteBuffer的一个子类,其实现了对内存的直接操作。
get过程