Java NIO教程 MappedByteBuffer

以前跟你們說過,要講MappedByteBuffer,如今我來履行承諾了。html

首先從大致上講一下MappedByteBuffer到底是什麼。從繼承結構上來說,MappedByteBuffer繼承自ByteBuffer,因此ByteBuffer有的能力它全有;像變更position和limit指針啦、包裝一個其餘種類Buffer的視圖啦,均可以。「MappedByteBuffer」爲什麼而來?吾輩心中亦有惑(熊貓人之謎的梗)用一個字來歸納就是java

爲何?由於它使用direct buffer的方式讀寫文件內容,這種方式的學名叫作內存映射。這種方式直接調用系統底層的緩存,沒有JVM和系統之間的複製操做,因此效率大大的提升了。並且因爲它這麼快,還能夠用它來在進程(或線程)間傳遞消息,基本上能達到和「共享內存頁」相同的做用,只不過它是依託實體文件來運行的。api

並且它還有另外一種能力。就是它可讓咱們讀寫那些由於太大而不能放進內存中的文件。有了它,咱們就能夠假定整個文件都放在內存中(實際上,大文件放在內存和虛擬內存中),基本上均可以將它看成一個特別大的數組來訪問,這樣極大的簡化了對於大文件的修改等操做。數組

下面咱們開始介紹它的用法了緩存

FileChannel提供了map方法來把文件映射爲MappedByteBuffer: MappedByteBuffer map(int mode,long position,long size); 能夠把文件的從position開始的size大小的區域映射爲MappedByteBuffer,mode指出了可訪問該內存映像文件的方式,共有三種,分別爲:
MapMode.READ_ONLY(只讀): 試圖修改獲得的緩衝區將致使拋出 ReadOnlyBufferException。
MapMode.READ_WRITE(讀/寫): 對獲得的緩衝區的更改最終將寫入文件;但該更改對映射到同一文件的其餘程序不必定是可見的(無處不在的「一致性問題」又出現了)。
MapMode.PRIVATE(專用): 可讀可寫,可是修改的內容不會寫入文件,只是buffer自身的改變,這種能力稱之爲」copy on write」oracle

再簡單的說一下,MappedByteBuffer較之ByteBuffer新增的三個方法app

  • fore()緩衝區是READ_WRITE模式下,此方法對緩衝區內容的修改強行寫入文件
  • load()將緩衝區的內容載入內存,並返回該緩衝區的引用
  • isLoaded()若是緩衝區的內容在物理內存中,則返回真,不然返回假

下面代碼終於出場了測試

int length = 0x8FFFFFF;//一個byte佔1B,因此共向文件中存128M的數據
try (FileChannel channel = FileChannel.open(Paths.get("src/c.txt"),
        StandardOpenOption.READ, StandardOpenOption.WRITE);) {
    MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, length);
    for(int i=0;i<length;i++) {
        mapBuffer.put((byte)0);
    }
    for(int i = length/2;i<length/2+4;i++) {
        //像數組同樣訪問
        System.out.println(mapBuffer.get(i));
    }
}

上面是MappedByteBuffer最基本的應用,而下面這段代碼主要是測試它到底有多快,this

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class TestMappedByteBuffer {
    private static int length = 0x2FFFFFFF;//1G
    private abstract static class Tester {
        private String name;
        public Tester(String name) {
            this.name = name;
        }
        public void runTest() {
            System.out.print(name + ": ");
            long start = System.currentTimeMillis();
            test();
            System.out.println(System.currentTimeMillis()-start+" ms");
        }
        public abstract void test();
    }
    private static Tester[] testers = {
        new Tester("Stream RW") {
            public void test() {
                try (FileInputStream fis = new FileInputStream(
                        "src/a.txt");
                        DataInputStream dis = new DataInputStream(fis);
                        FileOutputStream fos = new FileOutputStream(
                                "src/a.txt");
                        DataOutputStream dos = new DataOutputStream(fos);) {
                    
                    byte b = (byte)0;
                    for(int i=0;i<length;i++) {
                        dos.writeByte(b);
                        dos.flush();
                    }                   
                    while (dis.read()!= -1) {
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        },
        new Tester("Mapped RW") {
            public void test() {
                try (FileChannel channel = FileChannel.open(Paths.get("src/b.txt"),
                        StandardOpenOption.READ, StandardOpenOption.WRITE);) {
                    MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, length);
                    for(int i=0;i<length;i++) {
                        mapBuffer.put((byte)0);
                    }
                    mapBuffer.flip();
                    while(mapBuffer.hasRemaining()) {
                        mapBuffer.get();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        },
        new Tester("Mapped PRIVATE") {
            public void test() {
                try (FileChannel channel = FileChannel.open(Paths.get("src/c.txt"),
                        StandardOpenOption.READ, StandardOpenOption.WRITE);) {
                    MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.PRIVATE, 0, length);
                    for(int i=0;i<length;i++) {
                        mapBuffer.put((byte)0);
                    }
                    mapBuffer.flip();
                    while(mapBuffer.hasRemaining()) {
                        mapBuffer.get();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    };
    public static void main(String[] args) {
        for(Tester tester:testers) {
            tester.runTest();
        }
    }
}

先從總體上提一句上面的代碼,runTest()是一個模板方法,而且引用了一個未實現的test()方法;經過匿名內部類的實現,填充了測試內容。線程

再來講上面代碼的測試結果。用傳統流的方式,固然是最慢的,但應該是因爲用的數據量是1G,沒法所有讀入內存,因此它根本沒法完成測試。剩下兩種MapMode.READ_WRITEMapMode.PRIVATE各有特色,首先說MapMode.READ_WRITE,它的速度每次差異較大,在0.6s和8s之間波動,並且很不穩定。但MapMode.PRIVATE就穩得出奇,一直是1.1s到1.2s之間。但不管是哪一個速度都是十分驚人的。可是MappedByteBuffer也有不足,就是在數據量很小的時候,表現比較糟糕,那是由於direct buffer的初始化時間較長,因此建議你們只有在數據量較大的時候,在用MappedByteBuffer。

還要強調的一點是,MappedByteBuffer存在內存佔用和文件關閉等不肯定問題。被MappedByteBuffer打開的文件只有在垃圾收集時纔會被關閉,而這個點是不肯定的。javadoc裏是這麼說的:

A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected. ——JavaDoc

關於MappedByteBuffer就告訴你這麼多了,有什麼問題儘管提、有什麼想法隨時找我交流。

相關文章
相關標籤/搜索