學習JavaNIO-檔案記憶體映射
最近看了檔案記憶體映射。在NIO中,使用起來很方便。
檔案通過記憶體映射以後,訪問速度自然是提高了。
當然也有很多問題,現在我們來看看NIO中的記憶體對應檔。
Java代碼
- 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
- }
- }
public abstract class FileChannel extends AbstractChannel implements ByteChannel, GatheringByteChannel, ScatteringByteChannel { // This is a partial API listingpublic 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還有幾個函數可以介紹下。Java代碼
- public abstract class MappedByteBuffer extends ByteBuffer {
- // This is a partial API listing
- 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應答格式的檔案。我們來看看這個例子:Java代碼
- 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;
-
- /***
- * Dummy HTTP server using MappedByteBuffer.
- * Given a filename on the command line, pretend to be
- * a web server and generate an HTTP response containing
- * the file content preceded by appropriate headers The
- * data is send with a gathering write.
- *
- * @author Ron Hitchens (ron@ronsoft.com)
- *
- */
- 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){
- //file could not be opend; report problem
- ByteBuffer buf = ByteBuffer.allocate(128);
- String msg = MSG_404 + LINE_SEP;
- buf.put(bytes(msg));
- buf.flip();
- //Use the HTTP error response
- 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();
- // All the buffers have been prepared , write them out
- while(out.write(gather) > 0){
- //Empty body; loop until all buffers are empty
- }
- out.close();
- System.out.println("output written to "+ OUTPUT_FILE);
- }
-
- //Convert a String to its constituent bytes
- //from the ASCII character set
- private static byte[] bytes(String string)throws Exception {
- return (string.getBytes("US-ASCII"));
- }
- }