不學無數——JAVA中NIO再深刻

JAVA中NIO再深刻

在上一章節的JAVA中的I/O和NIO咱們學習瞭如何使用NIO,接下來再深刻了解一下關於NIO的知識。html

緩衝器內部的細節

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

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

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

方法名 解釋
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設爲-1數組

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

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

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

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

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

**注意此時前兩個字符已經互換了位置。**而後在第二輪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:建立一個寫時拷貝(copy-on-write)的映射文件

咱們能夠簡單的對比一下用內存映射文件對文件進行讀寫操做和用緩存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中兩個線程對於文件的競爭。文件鎖對於其餘的操做系統的進程是可見的,由於文件加鎖是直接映射到了本地操做系統的加鎖工具。

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

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()進行查詢。

參考文章

相關文章
相關標籤/搜索