JAVA-NIO-概述、Buffer(Buffer属性、API、创建)

NIO概述

我们的程序再读取数据的时候是按照:


程序读取数据模型

程序执行效率更多是由I/O效率决定的
在JDK4 中引入了NIO,可以最大限度的满足Java程序I/O的需求

NIO中有三大核心组件:Channel、Buffer、Selector

NIO与传统IO的区别

  • 传统的IO是面向流的,每次从流中读取一个或多个字节。只能向后读取,不能向前读取
  • NIO是面向缓冲区的,把数据读取到一个缓冲区中,可以在缓冲区中向前/向后移动,增加了程序的灵活性
  • 在NIO中,所有的数据都需要通过channel传输,通道可以直接将一块数据映射到内存中,Channel是双向的,可以读取数据,也可以保存数据。程序不能直接读写Channel通道,Channel只与Buffer缓冲区进行交互。
    读取模型:


    NIO操作数据模型
  • IO流是线程阻塞的,当我们在调用read()/write()读写数据时,线程会阻塞。直到数据读取完毕或者数据完全写入,在读写过程中,线程不能做其他的任务。而NIO不是线程阻塞的,当线程从Channel读取数据的时候如果通道中没有可用的数据,线程不会阻塞。线程可以做其他的任务

Buffer

Buffer缓冲区实际上就是一个数组,把数组的内容与信息包装成一个Buffer对象,它提供了一组访问这些信息的方法。缓冲区Buffer本质上就是一个数组

Buffer的属性

  • capacity:容量,是指缓冲区可以存储多少个数据。容量在创建Buffer缓冲区时指定大小,创建后不能再修改
    • 如果缓冲区满了,需要清空缓冲区后才能继续写数据
  • position:表示当前位置,即缓冲区写入/读取的位置,刚创建Buffer之后,position初始化为0,写入一个数据后,position就向后移动一个单元,最大值是capacity-1
    • 当buffer从写模式切换到读模式:position会被重置为0,从buffer的开始位置读取数据,每读取一个数据,position就向后移动一个单元
  • limit:指第一个不能读出或写入的位置。limit上限后面的单元既不能读也不能写。
    • 在Buffer缓冲区的写模式下,limit表示能够写入多少个数据
    • 在Buffer缓冲区的读模式下,limit表示最多可以读取多少个数据
  • mark标记:设置一个标记位置,可以调用mark()方法,将标记设置在对应的position位置。当调用reset()方法时,就把position设置为mark标记的位置
    • 0<= mark <= position <=limit <=capacity

下面我们用一个图说明

初始化

然后向缓冲区中存储6个字符

存储数据

调用flip()将缓冲区由写模式切换为读模式


写模式切换为读模式
  • 切换模式后,position重新置为0,limit置为position的位置

Buffer的常用API

在NIO中关键的Buffer:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer。这些Buffer覆盖了能够通过I/O发送的所有基本类型:byte、char、double、float、int、long、short。开发中常用的是ByteBuffer、CharBuffer

  • allocate(capacity):可以创建一个指定容量的缓冲区
  • put():用于向缓冲区中存储数据
  • get():用于从缓冲区中读取数据
  • compact():压缩数据。当缓冲区中还有未读完的数据,可以调用compact()进行压缩,将所有未读取的数据复制到Buffer的起始位置,把position设置到最后一个未读元素的后面。limit属性设置为capacity
  • capacity():返回缓冲区的大小
  • hasRemaing():用于判断当前的position后面是否还有可处理的数据,即判断position与limit是否还有数据
  • limit():返回limit上限的位置
  • mark():设置缓冲区的标志位置,这个值只能在0~position之间,可以通过reset()返回到这个位置
  • position():可以返回position当前位置
  • remaining():返回当前position位置与limit之间的数据个数
  • reset():将position设置到mark标记的位置
  • rewind():将position设置为0,然后取消mark标记
  • clear():清空缓冲区,仅仅是修改position标志为0,设置limit为capacity,缓冲区的数据还是存在的
  • flip():可以把缓冲区由写模式切换到读模式,写模式切换到读模式后,先把limit设置为position位置,再把position设置为0。
  • remaining():返回当前position到limit的数量

code演示

    //创建CharBuffer缓冲区对象
        CharBuffer charBuffer = CharBuffer.allocate(12);
        //输出Capacity、position、limit
        System.out.println("capacity" + charBuffer.capacity() + "position" + charBuffer.position() + "limit" + charBuffer.limit());//输出capacity12position0limit12
        //向缓冲区中存储数据 put
        charBuffer.put('D');
        charBuffer.put('L');
        charBuffer.put('6');
        charBuffer.put('5');
        charBuffer.put('0');//positon指到5的索引
        System.out.println("capacity" + charBuffer.capacity() + "position" + charBuffer.position() + "limit" + charBuffer.limit());
        //切换缓冲区的读写模式
        charBuffer.flip();
        //输出limit 5 positon 0 capacity 12
        System.out.println("capacity" + charBuffer.capacity() + "position" + charBuffer.position() + "limit" + charBuffer.limit());
        //调用get方法读取数据 get从position读取
        System.out.println(charBuffer.get());
        //读取一个数据后,position会向后移动
        System.out.println(charBuffer.position());//输出1
        //再向buffer中存储数据,由于此时position为1所以此时存储数据 数据将会存储在1的位置
        charBuffer.put('X');
        System.out.println(charBuffer.position());//输出2
        //设置一个mark标记
        charBuffer.mark();
        //再读取一个字符
        System.out.println(charBuffer.get());//取出的数据是6 position继续后移 position3
        System.out.println(charBuffer.position());
        //调用reset方法,重置position为mark标记位置
        charBuffer.reset();//position设置为mark位置 position为2
        System.out.println(charBuffer.position());
        //调用compact进行压缩,压缩会把buffer中未读取的数据复制到position位0的位置
        charBuffer.compact();//未读取的数据位L50
        System.out.println(charBuffer.position());//Position 为3
        //调用clear 清空缓冲区 清空后缓冲区的数据还在
        charBuffer.clear();
        System.out.println(charBuffer.position());

缓冲区的批量传输

public class NIOTest02 {
    public static void main(String[] args) {
        CharBuffer charBuffer = CharBuffer.allocate(17);
        //批量存储到缓冲区中
        charBuffer.put("hello who are you");
        //切换读模式
        charBuffer.flip();
        System.out.println(charBuffer);
        System.out.println(charBuffer.capacity() + "position" + charBuffer.position() + "limit" +
                charBuffer.limit());
        //创建数组存储缓冲区中的数据
        char[] dst = new char[14];
     /*   //批量传输时的大小是固定的,如果没有指定大小,意味着把数据填满数组中
        CharBuffer remainCharBuffer = charBuffer.get(dst);
        //输出  hello who are
        System.out.println(dst);
        //输出 you 剩余的数据
        System.out.println(remainCharBuffer);
        //remaining表示剩余数量
        charBuffer.get(dst,0,charBuffer.remaining());
        System.out.println(Arrays.toString(dst));*/
        //循环读取缓冲区的数据
        charBuffer.clear();
        while (charBuffer.hasRemaining()) {
            int remaining = charBuffer.remaining();
            int len = Math.min(dst.length, remaining);
            charBuffer.get(dst, 0, len);
            System.out.println(new String(dst, 0, len));
        }
        //批量写入数据
        char[] contents = {'a', 'b', 'c', 'd'};
        //把字符数组中的数据保存到缓冲区中,空间不够会报异常
//        charBuffer.put(contents);
        //调整position
        charBuffer.position(14);
        charBuffer.put(contents, 0, charBuffer.remaining());
        charBuffer.flip();
        System.out.println(charBuffer);
    }
}

缓冲区的创建方式

  1. 分配操作 allocate() 分配一个私有的指定容量大小的数组来存储元素
  2. 包装操作创建缓冲区 使用提供的数组作为存储空间来存储缓冲区中的数据,不再分配其他空间
   //分配方式创建缓冲区
        CharBuffer charBuffer = CharBuffer.allocate(16);
        //包装方式创建缓冲区
        char[] myArray = new char[16];
        CharBuffer wrapBuffer = CharBuffer.wrap(myArray);

我们通过put方式向缓冲区中保存数据,会直接影响到数组
对数组进行任何修改,也会影响缓冲区对象

        char[] myArray = new char[16];
        CharBuffer wrapBuffer = CharBuffer.wrap(myArray);
        //调用put向缓冲区中添加数据
        wrapBuffer.put("hello");
        //查看数组
        System.out.println(Arrays.toString(myArray));
        //对数组进行修改,看是否会影响缓冲区
        myArray[0]='x';
        //读取buffer中的数据
        wrapBuffer.flip();
        wrapBuffer.position(0);
        System.out.println(wrapBuffer);

无论使用allocat()还是通过wrap()创建的缓冲区都是间接的,简介缓冲区都会使用备份数组

  • hasArray() 可以判断是否有一个可存取的备份数组 如果返回true,可以通过array()返回缓冲区对象使用的备份数组的引用
        //判断是否有备份数组
        if (wrapBuffer.hasArray()) {
            //获取备份数组
            char[] array = wrapBuffer.array();
            System.out.println(array);
        }

缓冲区的复制与分割

  • duplicate():复制一个缓冲区
        //创建缓冲区
        CharBuffer charBuffer = CharBuffer.allocate(16);
        //将数据存储到缓冲区
        charBuffer.put("hello");
        //position = 5
        System.out.println(charBuffer.position()+"capacity"+charBuffer.capacity()
        +"limit"+charBuffer.limit());

        //缓冲区的复制
        CharBuffer duplicate = charBuffer.duplicate();
        System.out.println(duplicate.position()+"capacity"+duplicate.capacity()
        +"limit"+duplicate.limit());
        duplicate.flip();
        System.out.println(duplicate);

实际上 charbuffer与duplicate使用的是同一个数组

        //charbuffer与duplicate使用的是同一个数组
        duplicate.clear();
        duplicate.put("NIOworld");
        //存储数据后,查看charbuffer中的数据是否有变化
        charBuffer.flip();
        System.out.println(charBuffer);
  • slice():分割缓冲区,根据[position、limit)的区间创建一个新的缓冲区
   //分割缓冲区,设置dulicate的position为3
        duplicate.position(3);
        CharBuffer sliceBuffer = duplicate.slice();
        System.out.println("capacity"+sliceBuffer.capacity()+"position" + sliceBuffer.position() + "limit" + sliceBuffer.limit());
        System.out.println(sliceBuffer);

你可能感兴趣的:(JAVA-NIO-概述、Buffer(Buffer属性、API、创建))