學以至用——JAVA中NIO再深刻

緩衝器內部的細節

Buffer數據能夠高效地訪問及操做這些數據的四個索引 組成。這四個索引是sql

  • mark :標記,就像遊戲中設置了一個存檔同樣,能夠調用 reset() 方法進行迴歸到mark標記的地方。
  • position :位置,其實緩衝器實際上就是一個美化過的數組,從通道中讀取數據就是放到了底層的數組。因此其實就像索引同樣。因此positon變量跟蹤已經寫了多少數據。
  • limit :界限,即代表還有多少數據須要取出,或者還有多少空間可以寫入。
  • capacity :容量,代表緩衝器中能夠存儲的最大容量。

在緩衝器中每個讀寫操做都會改變緩衝器的狀態,用於反應所發生的變化。經過記錄和跟蹤這些變化,緩衝器就可以內部地管理本身的資源。下面是用於設置和復位索引以及查詢其索引值的方法數組

方法名 解釋
capacity() 返回緩衝器的容量
clear() 清空緩衝器,將position設置爲0,limit設置容量。調用此方法複寫緩衝器
flip() 將limit設置爲position,position設置爲0.此方法用於準備從緩衝區讀取已經寫入的數據
limit() 返回limit值
limit(int lim) 設置limit值
mark() 將mark設置爲positon
position() 返回position的值
position(int pos) 設置postion的值
remaining() 返回limit-position的值

接下來咱們寫個例子模擬這四個索引的變化狀況,例若有一個字符串 BuXueWuShu 。咱們交換相鄰的字符。緩存

private static void symmetricScranble(CharBuffer buffer){
        while (buffer.hasRemaining()){
            buffer.mark();
            char c1 = buffer.get();
            char c2 = buffer.get();
            buffer.reset();
            buffer.put(c2).put(c1);
        }
    }

複製代碼
public static void main(String[] args) {
        char [] data = "BuXueWuShu".toCharArray();
        ByteBuffer byteBuffer = ByteBuffer.allocate(data.length*2);
        CharBuffer charBuffer = byteBuffer.asCharBuffer();
        charBuffer.put(data);
        System.out.println(charBuffer.rewind());
        symmetricScranble(charBuffer);
        System.out.println(charBuffer.rewind());
        symmetricScranble(charBuffer);
        System.out.println(charBuffer.rewind());
    }

複製代碼

rewind() 方法是將 position 設爲0 , mark 設爲-1app

在剛進入 symmetricScranble () 方法時的各個索引以下圖所示dom

而後第一次調用了 mark() 方法之後就至關於給 mark 賦值了,至關於在此設置了一個回檔點。此時索引以下所示工具

而後每次調用 get() 方法 Position 索引都會改變,在第一次調用了兩次 get() 方法之後,各個索引以下post

而後調用了 reset() 方法另 Position=Mark ,此時的索引以下學習

而後每次調用 put() 方法也會改變 Position 索引的值,spa

注意此時前兩個字符已經互換了位置。而後在第二輪while開始再次改變了 Mark 索引的值,各個索引以下操作系統

此時咱們應該就知道前面咱們說的調用 clear() 方法並不會清除緩衝器裏面的數據的緣由了,由於只是將其索引變了而已。

內存映射文件

內存映射文件不是Java引入的概念,而是操做系統提供的一種功能,大部分操做系統都支持。

內存映射文件容許咱們建立和修改那些由於太大而不能放入內存的文件。有了內存映射文件,咱們就能夠假定整個文件都放在內存中,並且能夠徹底將其視爲很是大的數組進行訪問。因此對於文件的操做就變爲了對於內存中的字節數組的操做,而後對於字節數組的操做會映射到文件中。這種映射能夠映射整個文件,也能夠只映射文件中的一部分。何時字節數組中的的操做會映射到文件上呢?這是由操做系統內部決定的。

內存放不下整個文件也沒關係,操做系統會自動進行處理,將須要的內容讀到內存,將修改的內容保存到硬盤,將再也不使用的內存釋放。

如何用NIO將文件映射到內存中呢?下面有個小例子表示將文件的前1024個字節映射到內存中。

FileChannel fileChannel = new FileInputStream("").getChannel();
MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
複製代碼

建立一個內存映射文件只須要在通道中調用 map() 方法便可, MapMode 有如下三個參數

READ_ONLY
READ_WRITE
PRIVATE

咱們能夠簡單的對比一下用內存映射文件對文件進行讀寫操做和用緩存 Buffer 對文件進行讀寫操做的速度比較。

public static void main(String[] args) throws IOException {
        String fileName="/Users/hupengfei/Downloads/a.sql";
        long t1=System.currentTimeMillis();
        FileChannel fileChannel = new RandomAccessFile(fileName,"rw").getChannel();
        IntBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size()).asIntBuffer();
        map.put(0);
        for (int i = 1; i < 50590; i++) {
            map.put(map.get(i-1));
        }
        fileChannel.close();
        long t=System.currentTimeMillis()-t1;
        System.out.println("Mapped Read/Write:"+t);

        long t2=System.currentTimeMillis();
        RandomAccessFile randomAccessFile = new RandomAccessFile(new File(fileName),"rw");
        randomAccessFile.writeInt(1);
        for (int i = 0 ; i<50590;i++){
            randomAccessFile.seek(randomAccessFile.length()-4);
            randomAccessFile.writeInt(randomAccessFile.readInt());
        }
        randomAccessFile.close();
        long t22=System.currentTimeMillis()-t2;
        System.out.println("Stream Read/Write:"+t22);
    }

複製代碼

發現打印以下

Mapped Read/Write:29
Stream Read/Write:2439
複製代碼

文件越大,那麼這個差別會更明顯。

文件加鎖

在JDK1.4中引入了文件加鎖的機制,它容許咱們同步的訪問某個做爲共享資源的文件。對於同一文件競爭的兩個線程多是來自於不一樣的操做系統,也多是不一樣的進程,也多是相同的進程,例如Java中兩個線程對於文件的競爭。文件鎖對於其餘的操做系統的進程是可見的,由於文件加鎖是直接映射到了本地操做系統的加鎖工具。

分享一些知識點給你們但願能幫助到你們,或者從中啓發。

加Q君羊:821169538 領取免費學習資料,你們一塊兒討論交流學習

下面舉了一個簡單的關於文件加鎖的例子

public static void main(String[] args) throws IOException, InterruptedException {
        FileOutputStream fileOutputStream = new FileOutputStream("/Users/hupengfei/Downloads/a.sql");
        FileLock fileLock = fileOutputStream.getChannel().tryLock();
        if (fileLock != null){
            System.out.println("Locked File");
            TimeUnit.MICROSECONDS.sleep(100);
            fileLock.release();
            System.out.println("Released Lock");
        }
        fileLock.close();
    }

複製代碼

經過對 FileChannel 調用 tryLock() 或者 lock() 方法,就能夠得到整個文件的FileLock

tryLock()
lock()

調用 FileLock.release() 能夠釋放鎖。

固然也能夠經過如下的方式對於文件的部分進行上鎖

tryLock(long position,long size,boolean shared)
lock(long position,long size,boolean shared)
複製代碼

對於加鎖的區域是經過 positionsize 進行限定的,而第三個參數指定是否爲共享鎖。無參數的加鎖方法會對整個文件進行加鎖,甚至文件變大之後也是如此。其中鎖的類型是獨佔鎖仍是共享鎖能夠經過 FileLock.isShared() 進行查詢。

相關文章
相關標籤/搜索