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编程技术指南》

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