Java-NIO(2)

Java-NIO(2)

緩衝區

常規I/O操作存在很大缺點,主要是因爲它們是阻塞的,而NIO正是爲了解決常規I/O執行效率低的問題,採用非阻塞高性能運行的方式來避免出現笨拙的同步I/O帶來的效率低的問題

緩衝區Buffer,在NIO的使用中地位很高,因爲數據就是放到緩衝區,對數據進行處理的

NIO中的Buffer是一個用於存儲基本數據類型的容器,它以類似於數組有序的方式來存儲和組織數據,每個基本數據類型(除boolean)都有一個子類相對應

Buffer類

Buffer類及其子類都是抽象類,不能直接實例化,使用方式是將對應的數據類型的數組包裝進入緩衝區,藉助靜態方法wrap實現

API

NIO緩衝區中,有四個重要的參數:

  • capacity-容量

  • limit-限制

  • position-位置

  • mark-標記

四個之間的大小關係


0 <= mark <= position <= limit <= capacity

capacity

代碼包含元素的數量。值不能爲負數,且不可更改

方法:



/**
* Returns this buffer's capacity.
*
* @return  The capacity of this buffer
*/
public final int capacity() {
return capacity;
}


public class T1 {


    public static void main(String[] args) {

        byte[] bytes = new byte[]{1,2,3};

        short[] shorts = new short[]{1,2,3,4};

        int[] ints = new int[]{1,2,3,4,5};

        long[] longs = new long[]{1,2,3,4,5,6};

        float[] floats = new float[]{1,2,3,4,5,6,7};

        double[] doubles = new double[]{1,2,3,4,5,6,7,8};

        char[] chars = new char[]{'a','b','c','d','e'};

        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);

        ShortBuffer shortBuffer = ShortBuffer.wrap(shorts);

        IntBuffer intBuffer = IntBuffer.wrap(ints);

        LongBuffer longBuffer = LongBuffer.wrap(longs);

        FloatBuffer floatBuffer = FloatBuffer.wrap(floats);

        DoubleBuffer doubleBuffer = DoubleBuffer.wrap(doubles);

        CharBuffer charBuffer = CharBuffer.wrap(chars);

        print(byteBuffer);

        print(shortBuffer);

        print(intBuffer);

        print(longBuffer);

        print(floatBuffer);

        print(doubleBuffer);

        print(charBuffer);

    }

    private static void print(Buffer buffer) {

        System.out.println("Name : \n");

        System.out.println(buffer.getClass().getName() + "\n");

        System.out.println("Capacity : \n");

        System.out.println(buffer.capacity());

        System.out.println("----------------------------------");

    }

}


看下ByteBuffer中wrap方法:



//將字節數組包裝到緩衝區中。
//新緩衝區將由給定的字節數組支持; 也就是說,對緩衝區的修改將導致數組被修改,反之亦然。 新緩衝區的容量和限制將爲array.length,其位置爲零,其標記爲未定義,其字節順序爲BIG_ENDIAN。 它的支持數組將是給定的數組,其數組偏移量將爲零。
public static ByteBuffer wrap(byte[] array) {
return wrap(array, 0, array.length);
}


limit

何爲限制?緩衝區的限制代表了第一個不應該讀取或者寫入元素的index(索引),limit不可以爲負,且不可以大於capacity,假設position大於limit,那麼將position設置爲新的limit,假設mark已定義且大於新的limit,則丟棄mark

limit應用圖示:

圖1.png

測試:


public class T2 {

    public static void main(String[] args) {

        char[] chars = new char[]{'a','b','c','d','e'};

        CharBuffer charBuffer = CharBuffer.wrap(chars);

        System.out.println("capacity : \n");

        System.out.println(charBuffer.capacity());

        System.out.println("");

        System.out.println("Limit : \n");

        System.out.println(charBuffer.limit());

        System.out.println("");

        charBuffer.limit(3);

        System.out.println("設置調整之後 : \n");

        System.out.println("capacity : \n");

        System.out.println(charBuffer.capacity());

        System.out.println("");

        System.out.println("Limit : \n");

        System.out.println(charBuffer.limit());

        System.out.println("");

        charBuffer.put(0,'0');

        charBuffer.put(1,'1');

        charBuffer.put(2,'2');

        charBuffer.put(3,'3');//報錯

        charBuffer.put(4,'4');

        charBuffer.put(5,'5');

        charBuffer.put(6,'6');
        
    }
}

Limit使用場景就是當反覆向緩衝區存取數據時使用,如下圖,第一次向緩衝區存儲9個數據,然後讀取全部9個數據,完成之後再進行第二次向緩衝區存儲數據,第二次只存4個數據:

圖2.png

當讀取會出現問題,如果讀取全部1 2 3 4 E F G H I是錯誤的,所以要結合limit限制讀取的範圍,在E設置Limit,實現讀取1 2 3 4這4個正確的數據

position

何爲位置?它代表下一個要讀取或寫入元素的index,不能爲負,且不能大於limit

圖示:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZShlpodq-1586246156751)(https://upload-images.jianshu.io/upload_images/5653258-449c865aa0d5838e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

position對應index爲3,說明從此位置開始寫入或者讀取,直到limit結束


public class T3 {


    public static void main(String[] args) {

        char[] chars = new char[]{'a','b','c','d','e'};

        CharBuffer charBuffer = CharBuffer.wrap(chars);

        print(charBuffer);

        System.out.println("-----------");

        charBuffer.position(2);

        print(charBuffer);

        System.out.println("-----------");

        charBuffer.put('1');

        for (int i = 0; i < chars.length; i++) {
            System.out.println(chars[i] + "   ");
        }
    }

    private static void print(Buffer buffer) {

        System.out.println("Capacity : " + buffer.capacity()

        +"   " + "limit :  " + buffer.limit() + "   "
        + "position : " + buffer.position());

    }
}


remaining

方法remaining作用:返回當前位置與limit之間的元素個數

圖示:

圖4.png

源碼:



/**
* Returns the number of elements between the current position and the
* limit.
*
* @return  The number of elements remaining in this buffer
*/
public final int remaining() {
return limit - position;
}


public class T4 {

    public static void main(String[] args) {
        char[] chars = new char[]{'a','b','c','d','e'};

        CharBuffer charBuffer = CharBuffer.wrap(chars);

        print(charBuffer);

        System.out.println("-----------");

        charBuffer.position(2);

        print(charBuffer);

        System.out.println("-----------");

        System.out.println("remaining   " + charBuffer.remaining());

    }

    private static void print(Buffer buffer) {

        System.out.println("Capacity : " + buffer.capacity()

                +"   " + "limit :  " + buffer.limit() + "   "
                + "position : " + buffer.position());

    }
}

mark()

緩衝區位置設置標記

標記作用:緩衝區的標記是一個索引,在調用reset方法時,會將緩衝區的position位置重置爲該索引

緩衝區的mark類似於爬山時在關鍵路口設置路標,爲了在原路返回時找到回去的路



public class T6 {

    public static void main(String[] args) {


        char[] chars = new char[]{'a','b','c','d','e'};

        CharBuffer charBuffer = CharBuffer.wrap(chars);

        System.out.println("capacity        " + charBuffer.capacity());

        charBuffer.position(1);

        charBuffer.mark(); // 位置1設置標記

        System.out.println("position        " + charBuffer.position());

        System.out.println("-----------");


        charBuffer.position(2);

        System.out.println("position        " + charBuffer.position());


        charBuffer.reset();

        System.out.println("---------");

        System.out.println("reset \n");
        System.out.println("position        " + charBuffer.position());


    }
}

判斷只讀


public class T7 {

    public static void main(String[] args) {

        byte[] bytes = new byte[]{1,2,3};

        short[] shorts = new short[]{1,2,3,4};

        int[] ints = new int[]{1,2,3,4,5};

        long[] longs = new long[]{1,2,3,4,5,6};

        float[] floats = new float[]{1,2,3,4,5,6,7};

        double[] doubles = new double[]{1,2,3,4,5,6,7,8};

        char[] chars = new char[]{'a','b','c','d','e'};

        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);

        ShortBuffer shortBuffer = ShortBuffer.wrap(shorts);

        IntBuffer intBuffer = IntBuffer.wrap(ints);

        LongBuffer longBuffer = LongBuffer.wrap(longs);

        FloatBuffer floatBuffer = FloatBuffer.wrap(floats);

        DoubleBuffer doubleBuffer = DoubleBuffer.wrap(doubles);

        CharBuffer charBuffer = CharBuffer.wrap(chars);

        print(byteBuffer);
        print(shortBuffer);
        print(intBuffer);
        print(longBuffer);
        print(floatBuffer);
        print(doubleBuffer);
        print(charBuffer);
    }

    private static void print(Buffer buffer) {
        System.out.println("is readOnly ? : \n" +
                buffer.isReadOnly());
    }
}


直接緩衝區

何爲直接與非直接緩衝區?

圖示:

圖6.png

使用非直接緩衝區存取數據,需要將數據暫存在JVM的中間緩衝區,如果有頻繁的數據操作,則會大大降低軟件對於數據的吞吐量,效率低下,所以就使用直接緩衝區來解決這個問題


public class T8 {

    public static void main(String[] args) {

        ByteBuffer byteBuffer = ByteBuffer.allocate(100);

        System.out.println(byteBuffer.isDirect());

        ByteBuffer byteBuffer1 = ByteBuffer.allocateDirect(100);

        System.out.println(byteBuffer1.isDirect());

    }
}

還原緩衝區狀態

final Buffer clear():

還原緩衝區到初始狀態:

  • 位置設爲0

  • 限制設爲容量

  • 丟棄標記

源碼實現:


public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

此方法使用場景就是在對緩存區存儲數據之前調用此方法,如:


buf.clear();

in.read(buf);

但clear方法不是真正的清除緩存區中的數據,它的清除是通過將position位置歸0,再執行寫入新數據的代碼,將最新數據由索引位置0開始進行覆蓋,新值覆蓋舊值,間接的清除數據


public class T9 {


    public static void main(String[] args) {

        byte[] bytes = new byte[]{1,2,3,4,5,6,7};

        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);

        byteBuffer.position(2);
        byteBuffer.limit(3);
        byteBuffer.mark();

        byteBuffer.clear();

        System.out.println("position: \n" + byteBuffer.position());

        System.out.println("");

        System.out.println("limit : \n" + byteBuffer.limit());

        System.out.println("");
        
    }
}


緩衝區反轉

源碼:



public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

效果:

  • 限制設爲當前位置

  • 位置設爲0

  • 如果定義了標記就丟棄

當向緩衝區存儲數據,然後再從緩衝區讀取數據時,就是使用flip方法的好地方:



A : buf.allocate(10);

B : buf.put(8);

C : 先向buf寫入數據

D : buf.flip();

E : 再從buf讀數據


圖示:
圖6.png




public class T2 {

    public static void main(String[] args) {

        CharBuffer charBuffer = CharBuffer.allocate(20);

        print(charBuffer);

        charBuffer.put(" I am from China");

        System.out.println("-----------------------------");
        print(charBuffer);
        System.out.println("-----------------------------");
        charBuffer.position(0);
        print(charBuffer);
        System.out.println("-----------------------------");

        // 以下循環多輸出了6個空格 爲無效的數據
        // 正確的應該是隻打印前面有效的字符 空格不在輸出
        for (int i = 0; i < charBuffer.limit(); i++) {
            System.out.println(charBuffer.get());
        }

        /*
        上面爲錯誤的讀取數據示例
         */
        System.out.println("-----------------------------");

        print(charBuffer);
        System.out.println("-----------------------------");

        // 還原緩衝區狀態
        charBuffer.clear();

        print(charBuffer);
        System.out.println("-----------------------------");

        charBuffer.put(" I ams usa");

        print(charBuffer);
        System.out.println("-----------------------------");

        /*
        以下操作是自己手動實現了 flip方法的邏輯
         */
        charBuffer.limit(charBuffer.position());

        charBuffer.position(0);

        print(charBuffer);
        System.out.println("-----------------------------");

        for (int i = 0; i < charBuffer.limit(); i++) {
            System.out.println(charBuffer.get());
        }

    }

    private static void print(Buffer buffer){
        System.out.println("position : \n"
        + buffer.position()
        +"\n"
        +"limit : \n"
        +buffer.limit());
    }
}


判斷是否有底層實現的數組

源碼:


public final boolean hasArray() {
    return (hb != null) && !isReadOnly;
}


public class T3 {

    public static void main(String[] args) {

        ByteBuffer byteBuffer = ByteBuffer.allocate(100);

        byteBuffer.put((byte) 1);
        byteBuffer.put((byte) 2);

        System.out.println(byteBuffer.hasArray());

        /* 對直接緩衝區判斷 */

        ByteBuffer dircet_byteBuffer = ByteBuffer.allocateDirect(100);
        dircet_byteBuffer.put((byte) 1);
        dircet_byteBuffer.put((byte) 2);
        System.out.println(dircet_byteBuffer.hasArray());

    }
}


  • 打印true是因爲使用了byte[] hb存儲數據,所以hb[]對象爲非空,結果就是true

  • 打印false是因爲代表byte[] hb數組爲null,數據直接存在內存了

判斷當前位置與限制之間是否還有剩餘元素

源碼:


  public final boolean hasRemaining() {
        return position < limit;
    }

以及返回當前位置與限制之間的元素個數:


    public final int remaining() {
        return limit - position;
    }


    public static void main(String[] args) {

        byte[] bytes = new byte[]{1,2,3,4,5};

        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);

        byteBuffer.limit(3);

        byteBuffer.position(2);

        System.out.println("hasRemainging : \n"
        +byteBuffer.hasRemaining()
        +"Remainging : \n"
        +byteBuffer.remaining());


    }

重繞緩衝區

源碼實現:

    public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }

在一系列通道重新寫入或獲取的操作之前調用此方法:

     
out.write(buf);    // Write remaining data
buf.rewind();      // Rewind buffer
buf.get(array);    // Copy data into array</pre></blockquote>

rewind():標記清除,位置position歸0,limit不變

rewind經常在重新讀取緩衝區數據時使用

而clear()方法主要使用場景是在對緩衝區進行存儲數據之前調用

flip()是縮小limit範圍

三者側重點不同:

  • rewind:側重於在“重新”,在重新讀取,重新寫入時使用

  • clear():側重於還原一切狀態

  • flip():側重於substring截取


public class T5 {

    public static void main(String[] args) {

        byte[] bytes = new byte[]{1,2,3,4,5,6,7,8};

        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);

        print(byteBuffer);

        System.out.println("");

        byteBuffer.position(1);
        byteBuffer.limit(3);
        byteBuffer.mark();

        print(byteBuffer);

        System.out.println("");

        byteBuffer.rewind();

        print(byteBuffer);

        byteBuffer.reset();


    }
    private static void print(Buffer buffer){
        System.out.println("capacity : \n"
                + buffer.capacity()
                +"\n"
                +"limit : \n"
                +buffer.limit()
        +"\n"
        +"position : \n"
        + buffer.position());
    }
}

List.toArray(T[])轉出數組類型


public class T6 {

    public static void main(String[] args) {

        ByteBuffer b1 = ByteBuffer.wrap(new byte[]{'1','2','3'});
        ByteBuffer b2 = ByteBuffer.wrap(new byte[]{'1','2','3'});
        ByteBuffer b3 = ByteBuffer.wrap(new byte[]{'1','2','3'});
        ByteBuffer b4 = ByteBuffer.wrap(new byte[]{'1','2','3'});
        ByteBuffer b5 = ByteBuffer.wrap(new byte[]{'1','2','3'});

        List<ByteBuffer> list = new ArrayList<>();
        list.add(b1);
        list.add(b2);
        list.add(b3);
        list.add(b4);
        list.add(b5);

        ByteBuffer[] byteBuffers = new ByteBuffer[list.size()];
        list.toArray(byteBuffers);

        System.out.println("--------------------------");

        
        System.out.println(byteBuffers.length);

        for (int i = 0; i < byteBuffers.length; i++) {

            ByteBuffer byteBuffer = byteBuffers[i];

            while (byteBuffer.hasRemaining()) {

                System.out.println((char)byteBuffer.get());

            }

            System.out.println("--------------------------");

        }



    }
}


參考資料

《NIO與Socket編程技術指南》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章