最近看了文件内存映射。在NIO中,使用起来很方便。
文件通过内存映射以后,访问速度自然是提高了。
当然也有很多问题,现在我们来看看NIO中的内存映射文件。
- public abstract class FileChannel extends
- AbstractChannel implements ByteChannel, GatheringByteChannel, ScatteringByteChannel {
-
- public abstract MappedByteBuffer map (MapMode mode, long position,long size)
- public static class MapMode {
- public static final MapMode READ_ONLY
- public static final MapMode READ_WRITE
- public static final MapMode PRIVATE
- }
- }
public abstract class FileChannel extends
AbstractChannel implements ByteChannel, GatheringByteChannel, ScatteringByteChannel {
// This is a partial API listing
public abstract MappedByteBuffer map (MapMode mode, long position,long size)
public static class MapMode {
public static final MapMode READ_ONLY
public static final MapMode READ_WRITE
public static final MapMode PRIVATE
}
}
内存映射文件,是通过一个FileChannel来完成的,返回的MappedByteBuffer,也是一种ByteBuffer。这里有三种映射模式。主要针对这三种映射模式的差异说说。
根据map函数,我们得知,我们可以映射部分文件,也可以全部映射。
如果请求的大小,size超出文件,那么文件会相应的增长以对应映射。如果是Integer.MAX_VALUE,那么文件就会达到2.1GB
当然,如果你请求的只是一个只读文件,而你的size超出文件大小,那么就会抛出IOException。
对于 MapMode的前两种,READ_ONLY和READ_WRITE来说,都很好理解。要注意的而是,如果在一个只读文件上使用READ_WRITE,是要抛出NonWritableChannelException异常的。
MapMode.PRIVATE模式很有意思,称为“写时拷贝”(copy-on-write)的映射。这是操作系统中常用的技术。比如创建一个子进程时,子进程共享父进程的地址空间,当子进程修改空间的时候,才会拷贝要修改的部分。对于PRIVATE模式来说,只有使用put函数的时候,才会去拷贝。可能拷贝某一页或者几页。假设此时有其他的映射,是看不到本次put后的改变的。也就是说,私有的。
注意,没有unmap函数,也就是说,一旦map成功,那么即使FileChannel被关闭,映射依然存在,只有映射对应的MappedByteBuffer内存被回收,映射才算取消。
MappedByteBuffer直接反应磁盘文件,如果磁盘文件结构或大小改变,那么可能就无法访问文件。
MappedByteBuffer 直接内存访问,并不占用虚拟机内存。
MappedByteBuffer 也是ByteBuffer,所以可以用于SocketChannel之类的通道读写。
对于MappedByteBuffer还有几个函数可以介绍下。
- public abstract class MappedByteBuffer extends ByteBuffer {
-
- public final MappedByteBuffer load( )
- public final boolean isLoaded( )
- public final MappedByteBuffer force( )
- }
public abstract class MappedByteBuffer extends ByteBuffer {
// This is a partial API listing
public final MappedByteBuffer load( )
public final boolean isLoaded( )
public final MappedByteBuffer force( )
}
load函数用于从磁盘文件加载到内存。这个操作将会引起大量系统调用。慎用。一旦加载完成,再次访问文件,就和访问内存一样快。但是,这些都起决于操作系统底层调用。
isLoaded函数是用来判断文件是否完全加载到内存。
force则是强制同步内存文件到磁盘。也就是把MappedByteBuffer中的更改应用到磁盘中。这里包括了文件的元数据(最近一次访问,作者,创建时间等)。当然,如果是READ_ONLY或者PRIVATE模式,则不起作用。
下面这个例子,使用内存映射文件,生成一个HTTP应答格式的文件。我们来看看这个例子:
- package shaoxin.nio;
-
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.net.URLConnection;
- import java.nio.ByteBuffer;
- import java.nio.MappedByteBuffer;
- import java.nio.channels.FileChannel;
- import java.nio.channels.FileChannel.MapMode;
-
-
-
-
-
-
-
-
-
-
-
- public class MappedHttp {
- private static final String OUTPUT_FILE="MappedHttp.out";
- private static final String LINE_SEP = "\r\n";
- private static final String SERVER_ID = "Server: Ronsoft Dummy Server";
- private static final String HTTP_HDR =
- "HTTP/1.0 200 OK" + LINE_SEP + SERVER_ID + LINE_SEP;
- private static final String HTTP_404_HDR =
- "HTTP/1.0 404 Not Found" + LINE_SEP + SERVER_ID + LINE_SEP;
- private static final String MSG_404 = "Cound not open file: ";
- public static void main(String[] args) throws Exception{
- if(args.length < 1){
- System.err.println("Usage:filename");
- return;
- }
-
- String file = args[0];
- ByteBuffer header = ByteBuffer.wrap(bytes(HTTP_HDR));
- ByteBuffer dynhdrs = ByteBuffer.allocate(128);
- ByteBuffer[] gather = {header,dynhdrs,null};
- String contentType = "unknown/unknown";
- long contentLength = -1;
- try{
- FileInputStream fis = new FileInputStream(file);
- FileChannel fc = fis.getChannel();
- MappedByteBuffer filedata =
- fc.map(MapMode.READ_ONLY, 0, fc.size());
- gather[2] = filedata;
- contentLength = fc.size();
- contentType = URLConnection.guessContentTypeFromName(file);
- }catch(IOException e){
-
- ByteBuffer buf = ByteBuffer.allocate(128);
- String msg = MSG_404 + LINE_SEP;
- buf.put(bytes(msg));
- buf.flip();
-
- gather[0] = ByteBuffer.wrap(bytes(HTTP_404_HDR));
- gather[2] = buf;
- contentLength = msg.length();
- contentType = "text/plain";
- }
-
- StringBuffer sb = new StringBuffer();
- sb.append("Content-Length: " + contentLength);
- sb.append(LINE_SEP);
- sb.append("Content-Type: ").append(contentType);
- sb.append(LINE_SEP).append(LINE_SEP);
- dynhdrs.put(bytes(sb.toString()));
- dynhdrs.flip();
-
- FileOutputStream fos = new FileOutputStream(OUTPUT_FILE);
- FileChannel out = fos.getChannel();
-
- while(out.write(gather) > 0){
-
- }
- out.close();
- System.out.println("output written to "+ OUTPUT_FILE);
- }
-
-
-
- private static byte[] bytes(String string)throws Exception {
- return (string.getBytes("US-ASCII"));
- }
- }